Skip to content

Refactor Jinja macro helpers into dedicated module#212

Merged
leynos merged 4 commits intomainfrom
codex/refactor-macro-code-into-jinja_macros-module
Oct 17, 2025
Merged

Refactor Jinja macro helpers into dedicated module#212
leynos merged 4 commits intomainfrom
codex/refactor-macro-code-into-jinja_macros-module

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Oct 16, 2025

Summary

  • move the manifest macro helpers into manifest/jinja_macros.rs
  • update manifest.rs to consume the new module while keeping the parsing API unchanged
  • adjust the manifest tests to import the helper functions from the new module

Testing

  • make check-fmt
  • make lint
  • make test

https://chatgpt.com/codex/tasks/task_e_68f143fa8cc88322ad09678b2727e6ef

Summary by Sourcery

Extract Jinja macro helper functions from manifest.rs into a dedicated jinja_macros.rs module and update imports to maintain the existing manifest API

Enhancements:

  • Move all Jinja macro registration and invocation helpers into a new jinja_macros.rs module and remove them from manifest.rs
  • Modify manifest.rs to load and re-export the new module without altering the public parsing API

Tests:

  • Adjust manifest tests to import helper functions from the jinja_macros module instead of manifest.rs

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Oct 16, 2025

Reviewer's Guide

This refactor moves the manifest-specific Jinja macro helpers out of the main manifest module into a dedicated jinja_macros.rs submodule, preserving the existing public API and adjusting imports in both the manifest implementation and its tests.

Class diagram for the new jinja_macros module

classDiagram
    class CallerAdapter {
        +Value caller
        +*const State<'static, 'static> state
        +new(state: &State, caller: Value)
    }
    CallerAdapter --|> Object

    class MacroDefinition
    class Environment
    class State
    class Value
    class Kwargs
    class Error
    class ErrorKind

    class jinja_macros {
        +parse_macro_name(signature: &str) Result<String>
        +register_macro(env: &mut Environment, macro_def: &MacroDefinition, index: usize) Result<()>
        +register_manifest_macros(doc: &YamlValue, env: &mut Environment) Result<()>
        +call_macro_value(state: &State, macro_value: &Value, positional: &[Value], kwargs: Option<Kwargs>) Result<Value, Error>
        +make_macro_fn(template_name: String, macro_name: String) Fn(&State, Rest<Value>, Kwargs) -> Result<Value, Error>
    }
    jinja_macros ..> MacroDefinition
    jinja_macros ..> Environment
    jinja_macros ..> State
    jinja_macros ..> Value
    jinja_macros ..> Kwargs
    jinja_macros ..> Error
    jinja_macros ..> ErrorKind
    jinja_macros ..> CallerAdapter
Loading

Class diagram for manifest module after refactor

classDiagram
    class manifest {
        <<module>>
        +uses jinja_macros for macro helpers
    }
    manifest ..> jinja_macros
Loading

File-Level Changes

Change Details Files
Extract Jinja macro helpers into dedicated submodule
  • Created src/manifest/jinja_macros.rs containing parsing, registration, invocation, and adapter logic
  • Removed the same helper definitions from src/manifest.rs and adjusted their visibility to pub(crate)
src/manifest.rs
src/manifest/jinja_macros.rs
Wire up new module in manifest implementation and tests
  • Added mod jinja_macros; to src/manifest.rs and re-exported APIs unchanged
  • Updated src/manifest/tests.rs to import helper functions from manifest::jinja_macros
src/manifest.rs
src/manifest/tests.rs

Possibly linked issues

  • #Refactor src/manifest.rs: The PR moves Jinja macro helpers to a new submodule, directly addressing the issue's goal of splitting manifest.rs for Jinja processing.
  • #[issue_id]: The PR refactors src/manifest.rs by moving Jinja macro helpers into manifest/jinja_macros.rs, directly contributing to the issue's goal of splitting the module.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 16, 2025

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

Release Notes

  • Refactor

    • Reorganised internal macro-handling code for improved maintainability.
  • Documentation

    • Updated dependency management documentation with refined language for clarity.
  • Tests

    • Expanded test coverage for macro functionality and error handling scenarios.

Walkthrough

Extract Jinja macro handling from src/manifest.rs into a new module src/manifest/jinja_macros.rs, moving parsing, registration, compilation, invocation, caching and caller-adapter logic into that module while keeping environment setup and macro registration hook in the manifest entrypoint.

Changes

Cohort / File(s) Summary
Manifest entrypoint
src/manifest.rs
Remove inlined macro helpers and related imports; preserve environment setup and delegate macro registration to manifest::jinja_macros::register_manifest_macros.
New macro module
src/manifest/jinja_macros.rs
Add module implementing macro name parsing, macro template compilation & registration, batch registration from manifest YAML, macro invocation helper, macro wrapper closure, MacroCache/MacroInstance lifecycle, CallerAdapter for caller blocks, and thread/safety handling.
Manifest tests
src/manifest/tests.rs, tests/manifest_jinja_tests.rs
Update imports to use jinja_macros helpers; add tests for keyword-argument support, macro reusability/caching and manifest macro missing-body error.
Docs
docs/netsuke-design.md
Minor wording change in serde_yml maintenance paragraph; no behavioural changes.

Sequence Diagram(s)

sequenceDiagram
    participant Loader as Manifest loader
    participant Doc as Manifest YAML
    participant Env as Minijinja Environment
    participant Cache as MacroCache / Templates
    participant Caller as Caller code

    Note over Loader,Doc: Registration phase
    Loader->>Doc: read `macros` sequence
    Loader->>Env: register_manifest_macros(doc, env)
    Env->>Cache: compile macro body -> register template "__manifest_macro_{i}_{name}"
    Env-->>Loader: expose callable fn for macro name

    Note over Caller,Env: Invocation phase
    Caller->>Env: call macro function(name)(positional, kwargs)
    Env->>Cache: locate MacroInstance (state + compiled macro)
    Env->>Cache: if caller-block -> use CallerAdapter to attach outer state
    Cache->>Env: execute macro with State -> return Value/String
    Env-->>Caller: return result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🧩 Macros shuffled into their space,
Kept the names but cleared the place,
YAML feeds, templates compile,
Caller bridges cross the mile,
Tests ensure the dance runs ace.

Prompt for AI agents: Run the new macro tests and confirm thread-affinity assumptions hold on your platform.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Refactor Jinja macro helpers into dedicated module" is directly related to the main change in the changeset. The raw_summary confirms that the PR refactors macro-related helpers from src/manifest.rs into a new module at src/manifest/jinja_macros.rs, which is precisely what the title describes. The title is concise, specific, and clearly communicates the primary structural change without vague terminology or unnecessary detail.
Description Check ✅ Passed The pull request description is clearly related to the changeset. It accurately describes the three main objectives: moving manifest macro helpers into manifest/jinja_macros.rs, updating manifest.rs to consume the new module whilst preserving the existing parsing API, and adjusting manifest tests to import from the new module. These descriptions align with the actual changes shown in the raw_summary, and the description provides sufficient context about testing instructions and the refactoring rationale without being vague or off-topic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/manifest/jinja_macros.rs:256` </location>
<code_context>
-
-impl CallerAdapter {
-    fn new(state: &State, caller: Value) -> Self {
-        let ptr = ptr::from_ref(state).cast::<State<'static, 'static>>();
-        Self { caller, state: ptr }
-    }
</code_context>

<issue_to_address>
**issue (bug_risk):** Pointer casting in `CallerAdapter::new` is potentially unsafe.

This cast depends on lifetime assumptions that may not always hold. Explore safer alternatives or add runtime checks to detect incorrect usage.
</issue_to_address>

### Comment 2
<location> `src/manifest/jinja_macros.rs:251-252` </location>
<code_context>
-    }
-}
-
-unsafe impl Send for CallerAdapter {}
-unsafe impl Sync for CallerAdapter {}
-
-impl Object for CallerAdapter {
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Unsynchronized `Send`/`Sync` implementations may introduce subtle concurrency risks.

Consider adding module-level documentation outlining the thread safety risks and usage restrictions to help future maintainers avoid concurrency bugs.

```suggestion
/// # Thread Safety Notice
///
/// `CallerAdapter` is marked as `Send` and `Sync` via unsafe implementations.
/// This is necessary for integration with certain concurrency primitives, but
/// it is not inherently thread-safe. The underlying pointer to `State` is
/// shared across threads without synchronization, which may lead to subtle
/// concurrency bugs if the referenced `State` is mutated or not truly static.
///
/// ## Usage Restrictions
/// - Only use `CallerAdapter` in contexts where the referenced `State` is
///   guaranteed to be immutable or `'static`.
/// - Avoid mutating the underlying `State` from multiple threads.
/// - Future maintainers should carefully audit any changes to ensure thread
///   safety is preserved.
///
/// Failure to follow these guidelines may result in undefined behavior or
/// difficult-to-debug concurrency issues.

    state: *const State<'static, 'static>,
}
```
</issue_to_address>

### Comment 3
<location> `src/manifest/jinja_macros.rs:265-268` </location>
<code_context>
-unsafe impl Sync for CallerAdapter {}
-
-impl Object for CallerAdapter {
-    fn call(self: &Arc<Self>, _state: &State, args: &[Value]) -> Result<Value, Error> {
-        let state = unsafe { &*self.state };
-        self.caller.call(state, args)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Unsafe dereference of raw pointer in `CallerAdapter::call`.

Consider adding runtime assertions or debug checks to help detect invalid pointer usage during development.

```suggestion
    fn call(self: &Arc<Self>, _state: &State, args: &[Value]) -> Result<Value, Error> {
        debug_assert!(!self.state.is_null(), "CallerAdapter: state pointer is null before dereference");
        let state = unsafe { &*self.state };
        self.caller.call(state, args)
    }
```
</issue_to_address>

### Comment 4
<location> `src/manifest/jinja_macros.rs:186` </location>
<code_context>
-///
-/// The wrapper returns an error if the macro cannot be located or execution
-/// fails.
-fn make_macro_fn(
-    template_name: String,
-    macro_name: String,
</code_context>

<issue_to_address>
**issue (complexity):** Consider capturing the compiled macro value during registration to avoid repeated template lookups and state evaluations on each invocation.

```markdown
You can avoid the per-call `get_template` / `eval_to_state` / `lookup` dance by capturing the compiled `Template` (or even the `Value` for your macro) in the closure at registration time.  This flattens out one level of indirection, removes the string lookup, and makes your closure much simpler:

1. In `register_macro`, grab and clone the `Template` and the macro `Value` once:
```rust
pub(crate) fn register_macro(
    env: &mut Environment,
    macro_def: &MacroDefinition,
    index: usize,
) -> Result<()> {
    let name = parse_macro_name(&macro_def.signature)?;
    let template_name = format!("__manifest_macro_{index}_{name}");
    let tpl_source = format!("{{% macro {} %}}{}{{% endmacro %}}",
                             macro_def.signature, macro_def.body);
    env.add_template_owned(template_name.clone(), tpl_source)
       .with_context(|| format!("compile macro '{name}'"))?;

    // fetch and clone the template handle
    let tpl = env.get_template(&template_name)?
                 .clone();
    // evaluate once to grab the macro Value
    let init_state = tpl.eval_to_state(())?;
    let macro_obj = init_state
        .lookup(&name)
        .ok_or_else(|| anyhow::anyhow!("macro '{name}' not found"))?
        .clone();

    env.add_function(name.clone(), make_macro_fn(macro_obj));
    Ok(())
}
```

2. Change `make_macro_fn` to accept the pre-built `Value` and drop all of the get/eval/lookup internals:
```rust
fn make_macro_fn(
    macro_obj: Value,
) -> impl Fn(&State, Rest<Value>, Kwargs) -> Result<Value, Error> + Clone {
    move |state, Rest(args), kwargs| {
        // rebuild caller‐safe kwargs as before…
        let mut entries = Vec::new();
        for key in kwargs.args() {
            let mut val = kwargs.peek::<Value>(key)?;
            if key == "caller" {
                val = Value::from_object(CallerAdapter::new(state, val));
            }
            entries.push((key.to_string(), val));
        }
        let call_kwargs = if entries.is_empty() {
            None
        } else {
            Some(entries.into_iter().collect())
        };

        // now just call the pre‐built macro_obj
        let rendered_val = call_macro_value(state, &macro_obj, &args, call_kwargs)?;
        let s: String = rendered_val.into();
        let out = if matches!(state.auto_escape(), AutoEscape::None) {
            Value::from(s)
        } else {
            Value::from_safe_string(s)
        };
        Ok(out)
    }
}
```

This removes the extra template lookup and state‐eval on each invocation, flattens the nesting, and keeps all functionality intact.
</issue_to_address>

### Comment 5
<location> `src/manifest/jinja_macros.rs:133` </location>
<code_context>
-/// let rendered = call_macro_value(&state, &value, &[], Some(kwargs)).unwrap();
-/// assert_eq!(rendered.to_string(), "hi Ada");
-/// ```
-fn call_macro_value(
-    state: &State,
-    macro_value: &Value,
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural and unit tests for the new macro registration and invocation helpers.

The new module `jinja_macros.rs` introduces several functions for macro registration and invocation, but there are no corresponding behavioural or unit tests added in this pull request. Ensure that both types of tests are present to verify correct behaviour and catch regressions.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/manifest/jinja_macros.rs Outdated
Comment thread src/manifest/jinja_macros.rs Outdated
Comment thread src/manifest/jinja_macros.rs
Comment thread src/manifest/jinja_macros.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 410ccd0 and ded94ad.

📒 Files selected for processing (3)
  • src/manifest.rs (3 hunks)
  • src/manifest/jinja_macros.rs (1 hunks)
  • src/manifest/tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Every Rust module must begin with a module-level comment using //! explaining the module's purpose and utility
Document public APIs with Rustdoc comments (///) so documentation can be generated with cargo doc
Prefer immutable data; avoid unnecessary mut bindings
Handle errors with Result instead of panicking where feasible
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Clippy warnings must be denied; do not silence lints except as a last resort
Lint suppressions must be tightly scoped and include a clear reason; prefer #[expect(...)] over #[allow(...)]
Place function attributes after doc comments
Do not use return in single-line functions
Prefer single-line function definitions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use predicate functions for conditional criteria with more than two branches
Where a function is too long, extract meaningfully named helpers adhering to separation of concerns and CQRS
Where a function has too many parameters, group related parameters into meaningfully named structs
When returning large error data, consider using Arc to reduce data moved
Prefer .expect() over .unwrap() on Results/Options
Use concat!() to combine long string literals instead of escaping newlines with backslashes
Use NewTypes to model domain values; prefer explicit tuple structs; leverage newt-hype and the-newtype crates as described; provide appropriate From/AsRef/TryFrom implementations and avoid orphan-rule-violating impls
Keep each Rust code file under 400 lines; split long switches/dispatch tables by feature; move large test data to external files
Use functions and composition to avoid repetition; extract reusable logic; prefer declarative constructs (iterators/generators/comprehensions) when readable
Small, single-responsibility functions; obey command/query segregation
Name things precisely; booleans should use is/has/should prefixes
Comment why, not what; explain assumptions, e...

Files:

  • src/manifest/tests.rs
  • src/manifest/jinja_macros.rs
  • src/manifest.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/manifest/tests.rs
  • src/manifest/jinja_macros.rs
  • src/manifest.rs
**/*.{rs,md}

📄 CodeRabbit inference engine (AGENTS.md)

Use en-GB-oxendict spelling and grammar in comments and documentation (exceptions allowed for external API names)

Files:

  • src/manifest/tests.rs
  • src/manifest/jinja_macros.rs
  • src/manifest.rs
**/*{test,tests}.rs

📄 CodeRabbit inference engine (AGENTS.md)

Write unit and behavioural tests for new functionality and run them before and after changes

Files:

  • src/manifest/tests.rs
🧬 Code graph analysis (2)
src/manifest/tests.rs (1)
src/manifest/jinja_macros.rs (3)
  • parse_macro_name (33-52)
  • register_macro (64-81)
  • register_manifest_macros (93-106)
src/manifest.rs (1)
src/manifest/jinja_macros.rs (1)
  • register_manifest_macros (93-106)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64, macos-aarch64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64, macos-x86_64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64)
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64, windows-aarch64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: Sourcery review
  • GitHub Check: netsukefile (stable)
  • GitHub Check: netsukefile (1.89.0)
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (10)
src/manifest.rs (4)

10-14: LGTM! Clean import simplification.

The refactored imports correctly reflect the reduced responsibility of this module. Removing Arc, ptr, and the unused MiniJinja components (AutoEscape, State, Kwargs, Object, Rest) keeps the surface minimal.


65-65: LGTM! Module organization follows Rust conventions.

Declaring jinja_macros as a submodule correctly encapsulates the macro handling logic.


74-74: LGTM! Appropriate delegation to new module.

Importing register_manifest_macros from the new module maintains a clean API boundary whilst hiding implementation details.


138-138: LGTM! Macro registration preserved.

The call to register_manifest_macros maintains the existing behaviour whilst delegating to the new module.

src/manifest/jinja_macros.rs (5)

1-7: LGTM! Clear module documentation.

The module-level doc comment concisely explains the purpose and utility of the macro integration helpers.

Based on coding guidelines.


33-52: LGTM! Robust signature parsing.

The function correctly validates signature shape and extracts the macro identifier. Error messages are clear and actionable.


64-81: LGTM! Macro registration logic is sound.

The function compiles the macro body into a uniquely named template and registers a callable wrapper. Error context is appropriate.


133-148: LGTM! Kwargs handling is correct.

The function correctly appends Kwargs as a trailing positional value when present, matching MiniJinja's calling convention.


228-269: Unsafe code is justified; MiniJinja execution model confirms safety assumptions.

Verification confirms CallerAdapter's safety holds under MiniJinja 2.12.0:

  • Synchronous execution: MiniJinja guarantees macro/caller-block invocation runs synchronously as part of template rendering; no async suspension occurs.
  • State lifetime: The outer State reference (captured at line 207) outlives every CallerAdapter invocation because caller blocks execute immediately within the same render context.
  • Single-threaded: MiniJinja does not perform cross-thread migration during render; each rendering executes on one thread.

The pattern mirrors MiniJinja's built-in macro objects. Safety documentation (lines 236-247) accurately reflects the runtime's behaviour.

src/manifest/tests.rs (1)

3-5: LGTM! Test imports updated correctly.

The imports correctly reference the new jinja_macros module and pull MacroDefinition from crate::ast. Tests exercise the refactored helpers without requiring changes to test logic.

Comment thread src/manifest/jinja_macros.rs
Comment thread src/manifest/jinja_macros.rs
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Oct 16, 2025

@coderabbitai Have the following now been resolved?

Please address the comments from this code review:

## Overall Comments
- Given the size of the new macro‐related code in manifest.rs, consider refactoring it into a dedicated jinja_macros module to keep manifest.rs more focused and maintainable.
- The raw pointer use in CallerAdapter is fragile—document the safety invariants clearly or encapsulate the unsafety behind a safer API to prevent misuse down the road.

## Individual Comments

### Comment 1
<location> `src/manifest.rs:293-295` </location>
<code_context>
+        // MiniJinja requires keyword arguments to be appended as a trailing
+        // `Kwargs` value within the positional slice. Build that value lazily so
+        // we avoid allocating when no keywords were supplied.
+        let mut entries: Vec<(String, Value)> = Vec::new();
+        for key in kwargs.args() {
+            let mut value = kwargs.peek::<Value>(key)?;
+            if key == "caller" {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Special handling for 'caller' argument may need further validation.

Currently, 'caller' is wrapped without verifying if it's callable, which may cause runtime errors if an invalid value is provided. Please add a type check before wrapping.

```suggestion
            if key == "caller" {
                // Ensure the value is callable before wrapping
                if value.is_callable() {
                    value = Value::from_object(CallerAdapter::new(state, value));
                } else {
                    return Err(Error::new(
                        ErrorKind::InvalidOperation,
                        format!("'caller' argument must be callable, got {:?}", value.kind())
                    ));
                }
            }
```
</issue_to_address>

### Comment 2
<location> `src/manifest/tests.rs:95-22` </location>
<code_context>
-    let pattern = format!("{}/missing*", tmp.path().display());
-    let result = glob_paths(&pattern).expect("glob ok");
-    assert!(result.is_empty());
+fn register_manifest_macros_validates_shape(mut strict_env: Environment) {
+    let mut mapping = Mapping::new();
+    mapping.insert(
+        YamlValue::from("macros"),
+        YamlValue::from(vec![YamlValue::from(42)]),
+    );
+    let doc = YamlValue::Mapping(mapping);
+    let err = register_manifest_macros(&doc, &mut strict_env).expect_err("shape error");
</code_context>

<issue_to_address>
**suggestion (testing):** Test for invalid macro shape is good, but missing test for missing 'body' field.

Please add a test where the macro definition omits the 'body' field to verify error handling for missing required fields.
</issue_to_address>

### Comment 3
<location> `tests/manifest_jinja_tests.rs:247-261` </location>
<code_context>
+}
+
+#[rstest]
+fn manifest_macro_with_missing_signature_errors() {
+    let yaml = manifest_yaml(concat!(
+        "macros:\n",
+        "  - body: |\n",
+        "      hi\n",
+        "targets:\n",
+        "  - name: noop\n",
+        "    command: noop\n",
+    ));
+
+    let err = manifest::from_str(&yaml).expect_err("macro signature required");
+    let msg = format!("{err:?}");
+    assert!(msg.contains("signature"), "error message: {msg}");
+}
+
</code_context>

<issue_to_address>
**suggestion (testing):** Missing macro signature error is tested, but missing test for missing 'body'.

Please add a test case for a macro definition missing the 'body' field to verify error handling for all required fields.

```suggestion
#[rstest]
fn manifest_macro_with_missing_signature_errors() {
    let yaml = manifest_yaml(concat!(
        "macros:\n",
        "  - body: |\n",
        "      hi\n",
        "targets:\n",
        "  - name: noop\n",
        "    command: noop\n",
    ));

    let err = manifest::from_str(&yaml).expect_err("macro signature required");
    let msg = format!("{err:?}");
    assert!(msg.contains("signature"), "error message: {msg}");
}

#[rstest]
fn manifest_macro_with_missing_body_errors() {
    let yaml = manifest_yaml(concat!(
        "macros:\n",
        "  - signature: \"greet(name)\"\n",
        "targets:\n",
        "  - name: noop\n",
        "    command: noop\n",
    ));

    let err = manifest::from_str(&yaml).expect_err("macro body required");
    let msg = format!("{err:?}");
    assert!(msg.contains("body"), "error message: {msg}");
}
```
</issue_to_address>

### Comment 4
<location> `src/manifest.rs:19` </location>
<code_context>
-use std::{fs, path::Path};
+use std::{fs, path::Path, ptr, sync::Arc};
+
+/// A display name for a manifest source, used in error reporting.
+#[derive(Debug, Clone)]
+pub struct ManifestName(String);
</code_context>

<issue_to_address>
**issue (complexity):** Consider moving macro-related logic and ManifestName into separate submodules to keep manifest.rs focused and improve code organization.

```markdown
You’ve added a lot of new “macro plumbing” (parsing, registering, calling, the `CallerAdapter`), plus `ManifestName`, all into one huge `manifest.rs`.  I’d recommend moving all of that into two small sub‐modules:

1. `name.rs` — just your `ManifestName` newtype.  
2. `macros.rs` — all of your `parse_macro_name`, `register_*`, `make_macro_fn`, `call_macro_value`, and `CallerAdapter`.

That way `manifest.rs` stays focused on loading/parsing/rendering YAML+Jinja, and all the macro code lives elsewhere.

Here’s a sketch:

```rust
// src/manifest/name.rs
#[derive(Debug, Clone)]
pub struct ManifestName(String);

impl ManifestName {
    pub fn new(s: impl Into<String>) -> Self { Self(s.into()) }
    pub fn as_str(&self) -> &str { &self.0 }
}

impl Display for ManifestName {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}
```

```rust
// src/manifest/macros.rs
use minijinja::{
    Environment, 
    value::{Rest, Kwargs, Value}, 
    State, Error, ErrorKind
};
use crate::ast::MacroDefinition;
use serde_yml::Value as YamlValue;
use anyhow::Result;
use std::{sync::Arc, ptr};

pub fn parse_macro_name(sig: &str) -> Result<String> { /* ... */ }
pub fn register_macro(env: &mut Environment, def: &MacroDefinition, idx: usize) -> Result<()> { /* ... */ }
pub fn register_manifest_macros(doc: &YamlValue, env: &mut Environment) -> Result<()> { /* ... */ }

pub fn call_macro_value(
    state: &State,
    mv: &Value,
    pos: &[Value],
    kw: Option<Kwargs>,
) -> Result<Value, Error> { /* ... */ }

pub fn make_macro_fn(
    tmpl: String,
    name: String
) -> impl Fn(&State, Rest<Value>, Kwargs) -> Result<Value, Error> {
    move |state, Rest(args), kwargs| {
        /* ... */
    }
}

#[derive(Debug)]
struct CallerAdapter {
    inner: Value,
    ptr: *const State<'static, 'static>,
}

// safety comment…
unsafe impl Send for CallerAdapter {}
unsafe impl Sync for CallerAdapter {}

impl Object for CallerAdapter {
    fn call(self: &Arc<Self>, _st: &State, args: &[Value]) -> Result<Value, Error> {
        let st = unsafe { &*self.ptr };
        self.inner.call(st, args)
    }
}
```

Then in `manifest.rs`:

```rust
mod name;
mod macros;

use name::ManifestName;
use macros::register_manifest_macros;

// …later in from_str_named:
let mut jinja = Environment::new();
// …
register_manifest_macros(&doc, &mut jinja)?;
```

This keeps each file ~50 lines, clarifies intent, and separates concerns without losing functionality.
</issue_to_address>

### Comment 5
<location> `src/manifest.rs:180` </location>
<code_context>
+///
+/// Returns an error if the YAML shape is invalid, any macro signature is
+/// malformed, or template compilation fails.
+fn register_manifest_macros(doc: &YamlValue, env: &mut Environment) -> Result<()> {
+    let Some(macros) = doc.get("macros").cloned() else {
+        return Ok(());
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural and unit tests for manifest macro registration, including error cases and macro invocation.

The new macro registration logic (register_manifest_macros, register_macro, make_macro_fn, etc.) must be covered by both behavioural and unit tests. Ensure tests demonstrate correct macro registration, invocation, error handling for invalid signatures, and integration with the manifest loader.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 6
<location> `src/ast.rs:59` </location>
<code_context>
 /// assert_eq!(manifest.targets.len(), 1);
 /// # Ok(()) }
 /// ```
+#[derive(Debug, Clone, Deserialize, Serialize)]
+#[serde(deny_unknown_fields)]
+pub struct MacroDefinition {
</code_context>

<issue_to_address>
**issue (review_instructions):** Add unit and behavioural tests for MacroDefinition struct and its deserialization from YAML.

The MacroDefinition struct and its deserialization should be covered by unit tests (for struct correctness and edge cases) and behavioural tests (for manifest parsing and macro usage).

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 7
<location> `src/ast.rs:59` </location>
<code_context>
 /// assert_eq!(manifest.targets.len(), 1);
 /// # Ok(()) }
 /// ```
+#[derive(Debug, Clone, Deserialize, Serialize)]
+#[serde(deny_unknown_fields)]
+pub struct MacroDefinition {
</code_context>

<issue_to_address>
**issue (review_instructions):** Missing `//!` module-level comment at the top of the file.

Please add a `//!` doc comment at the very top of this module to describe its purpose, as required by the review instructions.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.rs`

**Instructions:**
Every module **must** begin with a `//!` comment.

</details>
</issue_to_address>

### Comment 8
<location> `docs/netsuke-design.md:422` </location>
<code_context>

+**Maintenance risk.** `serde_yml` is archived upstream and carries unsoundness
+advisories. Netsuke relies on it today, but we will investigate a maintained
+successor such as `serde_yaml_ng`. A follow-up ADR will outline the migration
+plan and compatibility testing.
+
</code_context>

<issue_to_address>
**suggestion (review_instructions):** The phrase "we will investigate" uses a first person pronoun, which should be avoided per the instructions.

Consider rephrasing to passive voice, e.g., "A maintained successor such as `serde_yaml_ng` will be investigated."

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Avoid 2nd person or 1st person pronouns ("I", "you", "we")

</details>
</issue_to_address>

### Comment 9
<location> `docs/netsuke-design.md:422` </location>
<code_context>

+**Maintenance risk.** `serde_yml` is archived upstream and carries unsoundness
+advisories. Netsuke relies on it today, but we will investigate a maintained
+successor such as `serde_yaml_ng`. A follow-up ADR will outline the migration
+plan and compatibility testing.
+
</code_context>

<issue_to_address>
**suggestion (review_instructions):** This paragraph line exceeds 80 columns and should be wrapped accordingly.

Please wrap the paragraph so that no line exceeds 80 columns, except for headings and code blocks.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Paragraphs and bullets must be wrapped to 80 columns

</details>
</issue_to_address>

### Comment 10
<location> `docs/netsuke-design.md:736` </location>
<code_context>
 built-in definition. This mirrors Jinja's behaviour and follows `minijinja`
 semantics where later definitions shadow earlier ones.

+The manifest loader compiles each macro definition into an internal template
+and registers a wrapper function that evaluates the macro on demand. The
+wrapper constructs a fresh MiniJinja state for every invocation so macro calls
</code_context>

<issue_to_address>
**suggestion (review_instructions):** This paragraph line exceeds 80 columns and should be wrapped accordingly.

Please wrap the paragraph so that no line exceeds 80 columns, except for headings and code blocks.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Paragraphs and bullets must be wrapped to 80 columns

</details>
</issue_to_address>

### Comment 11
<location> `docs/roadmap.md:124` </location>
<code_context>
+
+  - [ ] Draft an ADR evaluating maintained replacements for `serde_yml`
+        (for example `serde_yaml_ng`) and record the migration decision.
+  - [ ] Run a migration spike with the preferred crate, exercising the manifest
+        fixtures to capture compatibility notes and required mitigations.
+
</code_context>

<issue_to_address>
**suggestion (review_instructions):** This bullet line exceeds 80 columns and should be wrapped accordingly.

Please wrap the bullet so that no line exceeds 80 columns.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Paragraphs and bullets must be wrapped to 80 columns

</details>
</issue_to_address>

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Oct 16, 2025

@coderabbitai please create a new GitHub issue for the following:

Since each macro invocation builds a fresh State and re‐evaluates the template, you may want to benchmark hot code paths and consider caching the compiled state if performance becomes an issue.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 16, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Create GitHub issue for macro state caching performance optimization
gh issue create \
  --title "Consider benchmarking and caching macro state for performance optimisation" \
  --body "During review of PR #212, a potential performance optimisation was identified:

Since each macro invocation builds a fresh State and re-evaluates the template, consider benchmarking hot code paths and determining whether caching the compiled state would provide meaningful performance improvements.

**Related PR:** #212  
**Requested by:** @leynos" \
  --assignee leynos

Length of output: 44


@leynos: I've created issue #213 for the performance optimisation suggestion regarding macro state caching and benchmarking: #213

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 16, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

echo "=== Checking src/manifest/jinja_macros.rs for safety documentation ==="
cat src/manifest/jinja_macros.rs | head -100

echo -e "\n=== Checking for caller validation in jinja_macros.rs ==="
rg -n -A5 -B5 "caller" src/manifest/jinja_macros.rs

echo -e "\n=== Checking src/manifest/tests.rs for missing body test ==="
rg -n "missing.*body|body.*missing" src/manifest/tests.rs

echo -e "\n=== Checking tests/manifest_jinja_tests.rs for missing body test ==="
rg -n "missing.*body|body.*missing" tests/manifest_jinja_tests.rs

echo -e "\n=== Checking src/ast.rs for //! module comment ==="
head -10 src/ast.rs

echo -e "\n=== Checking docs/netsuke-design.md around line 422 ==="
sed -n '420,430p' docs/netsuke-design.md

echo -e "\n=== Checking docs/netsuke-design.md around line 736 ==="
sed -n '734,745p' docs/netsuke-design.md

echo -e "\n=== Checking docs/roadmap.md around line 124 ==="
sed -n '122,130p' docs/roadmap.md

Length of output: 8213


🏁 Script executed:

#!/bin/bash

echo "=== Checking exact line lengths in docs/netsuke-design.md around line 422 ==="
sed -n '421,425p' docs/netsuke-design.md | awk '{print NR": (len="length($0)") "$0}'

echo -e "\n=== Checking exact line lengths in docs/netsuke-design.md around line 738 ==="
sed -n '737,741p' docs/netsuke-design.md | awk '{print NR": (len="length($0)") "$0}'

echo -e "\n=== Checking exact line lengths in docs/roadmap.md around line 124 ==="
sed -n '124,127p' docs/roadmap.md | awk '{print NR": (len="length($0)") "$0}'

echo -e "\n=== Check if there's a test for missing body field in manifest tests ==="
cat src/manifest/tests.rs | grep -A20 "missing_body\|body.*required"

echo -e "\n=== Check if there's a test for missing body field in behavioural tests ==="
cat tests/manifest_jinja_tests.rs | grep -A20 "missing_body\|body.*required"

echo -e "\n=== Check ManifestName location ==="
rg -n "struct ManifestName" src/

Length of output: 1398


🏁 Script executed:

#!/bin/bash

echo "=== Check for macro registration tests (Comment 5) ==="
rg -n "register_manifest_macros|register_macro" src/manifest/tests.rs tests/manifest_jinja_tests.rs

echo -e "\n=== Check for macro invocation tests ==="
rg -n "call_macro_value|make_macro_fn" src/manifest/tests.rs

echo -e "\n=== Check for MacroDefinition tests (Comment 6) ==="
rg -n "MacroDefinition" src/manifest/tests.rs tests/manifest_jinja_tests.rs tests/

echo -e "\n=== List all test functions in manifest tests ==="
rg -n "^fn.*test|^\s*#\[test\]" src/manifest/tests.rs | head -30

echo -e "\n=== List all test functions in behavioural tests ==="
rg -n "^fn.*test|^\s*#\[rstest\]" tests/manifest_jinja_tests.rs | head -30

Length of output: 2326


@leynos: Most of the comments have been addressed, but four items remain unresolved:

✅ Resolved (9 items)

  1. Overall comment 1 & Comment 4: Macro code refactored into src/manifest/jinja_macros.rs
  2. Overall comment 2: CallerAdapter now has extensive safety documentation (lines 344–377) ✅
  3. Comment 5: Comprehensive test coverage for macro registration (register_macro_handles_arguments, register_macro_supports_keyword_invocation, register_macro_forwards_caller, register_macro_is_reusable, register_manifest_macros_validates_shape, register_manifest_macros_supports_multiple, call_macro_value_supports_kwargs) ✅
  4. Comment 6: MacroDefinition tested in both unit and behavioural tests ✅
  5. Comment 7: src/ast.rs begins with a //! module comment ✅
  6. Comments 9, 10, 11: Markdown line-length issues fixed (all now ≤80 columns) ✅

❌ Not Resolved (4 items)

  1. Comment 1 (src/manifest/jinja_macros.rs:203-204): The caller argument is still wrapped without validation. Add a check to verify it's callable before wrapping in CallerAdapter.

  2. Comment 2 (src/manifest/tests.rs): No unit test for a macro definition missing the body field.

  3. Comment 3 (tests/manifest_jinja_tests.rs): No behavioural test for a macro definition missing the body field (e.g., manifest_macro_with_missing_body_errors).

  4. Comment 8 (docs/netsuke-design.md:422): The phrase "but we will investigate" still uses a first-person pronoun. Rephrase to passive voice (e.g., "A maintained successor will be investigated").

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Oct 16, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/manifest/tests.rs

Comment on file

//! Tests for manifest parsing and macro registration helpers.

use super::jinja_macros::{

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: register_macro_forwards_caller,register_macro_supports_keyword_invocation

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 16, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the two functions mentioned in the duplication report
echo "=== Examining register_macro_forwards_caller ==="
ast-grep --pattern $'fn register_macro_forwards_caller($$$) {
  $$$
}'

echo -e "\n=== Examining register_macro_supports_keyword_invocation ==="
ast-grep --pattern $'fn register_macro_supports_keyword_invocation($$$) {
  $$$
}'

# Also get context around these functions
echo -e "\n=== Full context from src/manifest/tests.rs ==="
cat src/manifest/tests.rs

Length of output: 7094


@leynos: The duplication stems from both tests following an identical four-step pattern. The cleanest fix is to consolidate them into the existing parameterised test register_macro_handles_arguments, which already uses #[case] for similar scenarios.

Suggested fix

Add the two test cases to the existing parameterised test:

#[rstest]
#[case("greet()", "Hello", "{{ greet() }}", "Hello")]
#[case("echo(text='hi')", "{{ text }}", "{{ echo() }}", "hi")]
#[case(
    "joiner(items)",
    "{{ items | join(',') }}",
    "{{ joiner(['a', 'b', 'c']) }}",
    "a,b,c"
)]
#[case(
    "show(name, excited=false)",
    "{{ name ~ ('!' if excited else '') }}",
    "{{ show('Netsuke', excited=true) }}",
    "Netsuke!"
)]
#[case("salute(name='friend')", "Hello {{ name }}", "{{ salute(name='Ada') }}", "Hello Ada")]
#[case("wrap(prefix, caller)", "{{ prefix }}{{ caller() }}", "{% call wrap('Hi ') %}World{% endcall %}", "Hi World")]
fn register_macro_handles_arguments(
    #[case] signature: &str,
    #[case] body: &str,
    #[case] template: &str,
    #[case] expected: &str,
    mut strict_env: Environment,
) {
    let macro_def = MacroDefinition {
        signature: signature.to_string(),
        body: body.to_string(),
    };
    register_macro(&mut strict_env, &macro_def, 0).expect("register");
    let rendered = render_with(&strict_env, template).expect("render");
    assert_eq!(rendered.trim(), expected);
}

Then delete the two standalone test functions register_macro_supports_keyword_invocation and register_macro_forwards_caller.

AI agent prompt

Refactor the duplicate test functions in src/manifest/tests.rs by consolidating `register_macro_supports_keyword_invocation` and `register_macro_forwards_caller` into the existing parameterised test `register_macro_handles_arguments`.

Steps:
1. Locate the test function `register_macro_handles_arguments` in src/manifest/tests.rs.
2. Add two new `#[case]` attributes before the function signature:
   - `#[case("salute(name='friend')", "Hello {{ name }}", "{{ salute(name='Ada') }}", "Hello Ada")]`
   - `#[case("wrap(prefix, caller)", "{{ prefix }}{{ caller() }}", "{% call wrap('Hi ') %}World{% endcall %}", "Hi World")]`
3. Delete the entire `register_macro_supports_keyword_invocation` function (lines 79-87).
4. Delete the entire `register_macro_forwards_caller` function (lines 90-99).
5. Ensure the file still compiles and all tests pass.

The refactoring eliminates structural duplication whilst preserving test coverage for keyword invocation and caller forwarding.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Oct 17, 2025

@coderabbitai Has the following now been resolved?

src/manifest/tests.rs

Comment on file

//! Tests for manifest parsing and macro registration helpers.

use super::jinja_macros::{

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: register_macro_forwards_caller,register_macro_supports_keyword_invocation

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 17, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the duplicate functions still exist in src/manifest/tests.rs
echo "=== Checking for register_macro_forwards_caller ==="
rg -n "fn register_macro_forwards_caller" src/manifest/tests.rs

echo -e "\n=== Checking for register_macro_supports_keyword_invocation ==="
rg -n "fn register_macro_supports_keyword_invocation" src/manifest/tests.rs

echo -e "\n=== Examining the register_macro_handles_arguments test ==="
ast-grep --pattern $'#[rstest]
$$$
fn register_macro_handles_arguments($$$) {
  $$$
}'

echo -e "\n=== Counting #[case] attributes in register_macro_handles_arguments ==="
rg -B50 "fn register_macro_handles_arguments" src/manifest/tests.rs | rg "#\[case\(" | wc -l

Length of output: 253


@leynos: Yes, the code duplication issue has been resolved

The script confirms:

  • Both register_macro_forwards_caller and register_macro_supports_keyword_invocation functions have been removed from src/manifest/tests.rs
  • The register_macro_handles_arguments parameterised test now contains 14 test cases (up from the original count), indicating the duplicate test scenarios have been successfully consolidated

The refactoring eliminates the structural duplication whilst preserving complete test coverage for keyword invocation and caller forwarding.

🐰

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Oct 17, 2025

@coderabbitai pause

@leynos leynos force-pushed the codex/refactor-macro-code-into-jinja_macros-module branch from 8433cf2 to 018b676 Compare October 17, 2025 08:04
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 17, 2025

✅ Actions performed

Reviews paused.

@leynos leynos merged commit 7c9d179 into main Oct 17, 2025
14 of 15 checks passed
@leynos leynos deleted the codex/refactor-macro-code-into-jinja_macros-module branch October 17, 2025 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant