Skip to content

Refactor large parser modules into smaller components under 400 lines (#223)#259

Open
leynos wants to merge 22 commits into
mainfrom
refactor-parser-modules-d2nyz5
Open

Refactor large parser modules into smaller components under 400 lines (#223)#259
leynos wants to merge 22 commits into
mainfrom
refactor-parser-modules-d2nyz5

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Apr 23, 2026

Summary

  • Refactors large parser modules into smaller, focused components to improve maintainability and testability. No user-facing behavior changes.

Changes

Core Refactor

  • Introduced dedicated S-expression formatting for Expr via a new module: src/parser/ast/expr/sexpr.rs and wired it into the Expr API (Expr::to_sexpr moved from the main file).
  • Moved rule-body expression classification logic into a dedicated module: src/parser/ast/rule/classification.rs and updated rule.rs to delegate classification to it.
  • Extracted Pratt postfix handling into separate modules:
    • src/parser/expression/pratt/delay.rs
    • src/parser/expression/pratt/diff.rs
    • src/parser/expression/pratt/postfix.rs
    • Updated src/parser/expression/pratt.rs to wire in the new modules (mod delay; mod diff; mod postfix).
  • Adjusted module layout and imports to reflect the new structure, reducing file sizes and coupling in core parser files.

Tests and Fixtures

  • Reworked tests to rely on a structured fixtures system for expression parsing:
    • Added new test fixtures module: src/parser/tests/expression/fixtures.rs
    • Split valid expression fixtures into submodules (basic, collections, control_flow, postfix, structured) and wired them into a single expression_cases() entry point
    • Added error fixtures at: src/parser/tests/expression/fixtures/errors.rs
    • Added postfix error fixtures at: src/parser/tests/expression/fixtures/valid/postfix.rs and related fixtures modules
  • Introduced a dedicated sexpr rendering helper for tests (via the new sexpr.rs) and updated tests to use to_sexpr() for compact comparison of parsed structures.

Miscellaneous

  • Minor cleanup to remove inline to_sexpr implementation from Expr and rely on the new sexpr module.
  • Code organization improvements align with the 400-line module guideline mentioned in the task, while preserving functionality.

How to test

  • Run: cargo test
  • Specifically validate that expression parsing tests pass with the new fixtures layout and that S-expression representations render as expected via Expr::to_sexpr().
  • Ensure no public API changes in user-facing behavior; changes are internal restructuring and test shims.

Notes

  • This refactor improves maintainability and testability of parser components by modularizing: S-expression formatting, rule classification, and Pratt postfix handling.
  • If any downstream tooling relies on file locations, please update accordingly; public behavior remains unchanged.

◳ Generated by DevBoxer


ℹ️ Tag @devboxerhub to ask questions and address PR feedback

📎 Task: https://www.devboxer.com/task/4e7f979f-30bc-45b5-844c-4e890802856c

📝 Closes #223

Summary by Sourcery

Refactor parser internals and tests into smaller, focused modules while preserving existing parsing behavior.

Enhancements:

  • Extract rule body classification logic into a dedicated module and delegate from the main rule AST type.
  • Split Pratt parser postfix, diff, and delay handling into separate helper modules to reduce pratt.rs size and coupling.
  • Move S-expression formatting for expressions into its own module to decouple test-oriented rendering from the core AST definition.
  • Introduce a shared expression test fixtures module with structured valid and error cases to simplify and consolidate parser tests.

Tests:

  • Rewrite expression parser tests to consume structured fixtures for literals, control flow, postfix operations, and error conditions, improving reuse and coverage organization.

- Extract rule-body term classification into a dedicated module `rule/classification.rs`.
- Move S-expression formatting for Expr into a separate `expr/sexpr.rs` module.
- Split Pratt parser postfix parsing, delay-postfix, and diff-marker handling into separate files under `expression/pratt/`.
- Remove inline implementations and validations, organizing parser logic into coherent units.
- Revamp expression parser tests to use fixture modules, improving maintainability.

This refactor improves code organization, readability, and test structure without changing external behavior.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 301a4613-1ca0-4596-843e-4ad65d9c268b

📥 Commits

Reviewing files that changed from the base of the PR and between 1a6f9b8 and 324810e.

📒 Files selected for processing (5)
  • src/parser/ast/expr/sexpr.rs
  • src/parser/ast/rule/classification.rs
  • src/parser/expression/pratt/diff.rs
  • src/parser/expression/pratt/postfix.rs
  • tests/expression_var_and_call.rs
 _________________________________________
< Make it work, make it right, make it 🥕. >
 -----------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Walkthrough

Refactor large parser modules into focused submodules: extract S‑expression rendering, move rule-body term classification to a new module, split Pratt postfix parsing into postfix/diff/delay helpers, migrate expression test matrices into fixture modules, add test helpers for atom markers, and update Makefile/Cargo/docs.

Changes

Parser refactor cohort

Layer / File(s) Summary
S-expression rendering
src/parser/ast/expr.rs, src/parser/ast/expr/sexpr.rs
Extract Expr::to_sexpr and formatting helpers into sexpr.rs; remove the prior in-file implementation and add snapshot tests.
Rule‑body classification
src/parser/ast/rule.rs, src/parser/ast/rule/classification.rs
Move rule-body literal parsing/classification into classification.rs; delegate from rule.rs; implement assignment, for-loop, and aggregation classification with span-aware errors and duplicate-aggregation diagnostics.
Pratt postfix / diff / delay parsing
src/parser/expression/pratt.rs, src/parser/expression/pratt/postfix.rs, src/parser/expression/pratt/diff.rs, src/parser/expression/pratt/delay.rs
Split postfix parsing into dispatcher (postfix.rs) and helpers (diff.rs, delay.rs); thread pending diff-marker state, validate marker placement, and implement -<N> delay postfix parsing; centralise struct-literal guard scope logic.
Tests — fixtures and cases
src/parser/tests/expression.rs, src/parser/tests/expression/fixtures/*
Replace inline rstest parameter tables with fixture modules and case structs; refactor tests to iterate fixture generators for valid and error cases, including extensive postfix, collection, control-flow and structured fixtures.
Test utilities, integration tests, Makefile & docs
src/test_util/expressions.rs, src/test_util/mod.rs, tests/expression_postfix.rs, src/parser/tests/rules/body_terms.rs, Makefile, Cargo.toml, docs/*
Add atom_diff/atom_delay helpers and export atom_delay; update postfix integration test and add whitespace-only assignment unit test; make CARGO resolve via command -v with HOME fallback; add insta dev-dependency and developer guide/docs updates.

Sequence Diagram(s)

(The changes introduce a multi-component sequential flow in Pratt postfix parsing and satisfy the sequence-diagram conditions.)

sequenceDiagram
  participant Parser as Pratt parser
  participant Postfix as postfix.rs
  participant Diff as diff.rs
  participant Delay as delay.rs
  Parser->>Postfix: parse_postfix(lhs)
  Postfix->>Diff: handle_diff_marker() / apply_pending_diff()
  Postfix->>Delay: parse_delay_postfix()
  Postfix-->>Parser: final Expr (with AtomDiff/AtomDelay applied)
Loading

Possibly related issues

Possibly related PRs

  • leynos/whitaker#224: Both PRs change Makefile markdownlint targets (diff-vs-main behavior related).

Suggested labels

Issue

"A parser split, neat and wise,
modules shrink before your eyes.
Tests now fixture-driven, bright,
diffs and delays take flight.
Docs and Makefile set to rights."


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Tests contain vacuous assertions: diff test checks only variant type not wrapped expr; classification test never validates parsed pattern/value; trailing-comma span fix unimplemented. Check inner expr in diff test. Validate assignment details in classification test. Implement trailing-comma span fix. Add independent structural validation alongside snapshots.
Unit Architecture ❌ Error Postfix.rs discards 4 next_tok() results hiding spans. Trailing-comma error uses peek_span instead of comma span. Violates explicit fallibility and diagnostic precision flagged in reviews. Capture next_tok() spans; use comma_span for trailing-comma error line 151; remove handle_diff_marker defensive pattern.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references issue #223 and accurately summarises the core refactoring work: modularising large parser files below 400 lines.
Description check ✅ Passed The PR description comprehensively documents the refactoring scope, including module extractions, test fixture restructuring, and testing guidance aligned with the changeset.
Linked Issues check ✅ Passed The PR fully addresses issue #223 objectives: all four oversized modules (rule.rs, expr.rs, pratt.rs, expression.rs tests) are refactored below 400 lines through single-responsibility splits [#223].
Out of Scope Changes check ✅ Passed Changes remain focused on modularisation and test restructuring. Documentation additions (developers-guide.md, roadmap.md) and Makefile/Cargo.toml adjustments are supporting changes within the refactoring scope.
Docstring Coverage ✅ Passed Docstring coverage is 89.11% which is sufficient. The required threshold is 80.00%.
User-Facing Documentation ✅ Passed PR contains no user-facing functionality changes—only internal refactoring. Code is reorganised; test utilities added; developers-guide.md provided. Check does not apply.
Developer Documentation ✅ Passed Complete documentation: developers-guide.md documents module ownership; implementation notes reference #223; roadmap item marked complete; all five modules documented; files under 400-line limit.
Module-Level Documentation ✅ Passed All 15 new modules have //! docstrings explaining purpose, utility and relationships to other components consistently applied across parser and test modules.
Testing (Unit And Behavioural) ✅ Passed PR includes 13 unit tests across new modules covering local behaviour and error paths; 70+ fixture-driven behavioural tests exercise public parsing APIs without mocks or disguised unit tests.
Testing (Property / Proof) ✅ Passed Pure refactoring that relocates existing code to new modules. No new invariants, properties, axioms or proof assumptions introduced.
Testing (Compile-Time / Ui) ✅ Passed Snapshot tests implemented: two insta inline snapshots in sexpr.rs test meaningful S-expression semantics with stable deterministic serialisation; no compile-time/UI requiring trybuild.
Domain Architecture ✅ Passed Parser infrastructure reorganisation maintains clean boundaries; no domain logic leakage; domain layer unaffected; appropriate internal parser structure.
Observability ✅ Passed Internal refactoring only — no operational behaviour changes, no new failure modes, no new public APIs introduced. No observability additions required.
Security And Privacy ✅ Passed Pure refactoring PR with no security risks. No secrets, unsafe code, injection vulnerabilities, or sensitive data exposure in any new code, tests, or fixtures.
Performance And Resource Use ✅ Passed No performance regressions: to_sexpr() is test-only, Vec allocations unavoidable, Span clones happen outside hot loops, no blocking I/O or algorithmic degradation.
Concurrency And State ✅ Passed This PR is a structural refactoring of parser modules. It introduces no concurrency patterns, async code, locks, shared mutable state, or ordering concerns—the check is inapplicable.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-parser-modules-d2nyz5

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

codescene-delta-analysis[bot]

This comment was marked as outdated.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 23, 2026

Reviewer's Guide

Refactors parser internals by extracting S-expression formatting, rule-body classification, and Pratt postfix parsing into dedicated modules, and reworks expression tests around a reusable fixtures system without changing observable parsing behavior.

Sequence diagram for the new rule body term parsing pipeline

sequenceDiagram
    participant RuleBodyExpression
    participant RuleClassificationModule as classification
    participant PatternParser as parse_pattern
    participant ExpressionParser as parse_expression
    participant AggregationLogic as aggregation_tracking
    participant Errors as errors_vec

    RuleBodyExpression->>RuleClassificationModule: parse_rule_body_term(raw, literal_span, first_aggregation_span, errors)
    alt raw contains_assignment
        RuleClassificationModule->>PatternParser: parse_pattern(parts.pattern)
        PatternParser-->>RuleClassificationModule: Result_Pattern_or_errors
        alt pattern_ok
            RuleClassificationModule->>ExpressionParser: parse_expression(parts.value)
            ExpressionParser-->>RuleClassificationModule: Result_Expr_or_errors
            alt value_ok
                RuleClassificationModule-->>RuleBodyExpression: Some_RuleBodyTerm_Assignment
            else value_errors
                RuleClassificationModule->>Errors: append(shifted_errors)
                RuleClassificationModule-->>RuleBodyExpression: None
            end
        else pattern_errors
            RuleClassificationModule->>Errors: append(shifted_errors)
            RuleClassificationModule-->>RuleBodyExpression: None
        end
    else raw_is_expression
        RuleClassificationModule->>ExpressionParser: parse_expression(trimmed_raw)
        ExpressionParser-->>RuleClassificationModule: Result_Expr_or_errors
        alt expr_ok
            RuleClassificationModule->>AggregationLogic: classify_expression(expr, ctx)
            AggregationLogic-->>RuleClassificationModule: Option_RuleBodyTerm
            RuleClassificationModule-->>RuleBodyExpression: Option_RuleBodyTerm
        else expr_errors
            RuleClassificationModule->>Errors: append(errors)
            RuleClassificationModule-->>RuleBodyExpression: None
        end
    end
Loading

Class diagram for rule body classification and Expr S-expression formatting

classDiagram
    class Expr {
        +to_sexpr() String
    }

    class Literal {
    }

    class Pattern {
        +to_source() String
    }

    class MatchArm {
        pattern Pattern
        body Expr
    }

    class RuleBodyTerm {
    }

    class RuleAssignment {
        pattern Pattern
        value Expr
    }

    class RuleAggregation {
        project Expr
        key Expr
        source AggregationSource
    }

    class RuleForLoop {
        pattern Pattern
        iterable Expr
        guard Expr
        body_terms Vec_RuleBodyTerm_
    }

    class AggregationSource {
        <<enum>>
        GroupBy
        LegacyAggregate
    }

    class ClassificationContext {
        -literal_span Span
        -first_aggregation_span Option_Span_
        -errors Vec_SimpleSyntaxKind__
    }

    class ForLoopComponents {
        pattern Pattern
        iterable Expr
        guard Option_Expr_
        body Expr
    }

    class RuleClassifier {
        +parse_rule_body_term(raw String, literal_span Span, first_aggregation_span Option_Span_, errors Vec_SimpleSyntaxKind__) Option_RuleBodyTerm_
        -parse_assignment(parts AssignmentParts, literal_span Span) Result_Option_RuleBodyTerm__Vec_SimpleSyntaxKind___
        -classify_expression(expr Expr, ctx ClassificationContext) Option_RuleBodyTerm_
        -classify_aggregation_with_tracking(args Vec_Expr_, source AggregationSource, ctx ClassificationContext) Option_RuleBodyTerm_
        -classify_for_loop(components ForLoopComponents, ctx ClassificationContext) RuleBodyTerm
        -classify_for_body_with_aggregation_tracking(body Expr, ctx ClassificationContext) Vec_RuleBodyTerm_
        -aggregation_source_for(name String) Option_AggregationSource_
        -invocation_aggregation_source(callee Expr) Option_AggregationSource_
        -validate_aggregation(term RuleBodyTerm, literal_span Span, first_aggregation_span Option_Span_, errors Vec_SimpleSyntaxKind__) bool
        -aggregation_arity_error(literal_span Span, source AggregationSource) Vec_SimpleSyntaxKind__
        -multiple_aggregations_error(first_span Span, second_span Span) SimpleSyntaxKind
    }

    Expr --> Literal
    Expr --> Pattern
    Expr --> MatchArm

    RuleBodyTerm <.. RuleAssignment
    RuleBodyTerm <.. RuleAggregation
    RuleBodyTerm <.. RuleForLoop

    RuleForLoop "*" o-- "body_terms" RuleBodyTerm
    RuleAggregation --> AggregationSource

    RuleClassifier ..> ClassificationContext
    RuleClassifier ..> ForLoopComponents
    RuleClassifier ..> RuleBodyTerm
    RuleClassifier ..> RuleAggregation
    RuleClassifier ..> RuleAssignment

    ClassificationContext --> Span
    ClassificationContext --> AggregationSource

    ForLoopComponents --> Pattern
    ForLoopComponents --> Expr
Loading

File-Level Changes

Change Details Files
Extracted Expr S-expression rendering into a dedicated module and updated call sites.
  • Moved Expr::to_sexpr implementation and helpers from expr.rs into a new expr/sexpr.rs module.
  • Reimplemented to_sexpr and helper functions (format_nary, format_match, etc.) with largely identical behavior but localized to expr/sexpr.rs.
  • Adjusted imports and module wiring so tests and other code now use the new sexpr module rather than inline methods in the main Expr definition.
src/parser/ast/expr.rs
src/parser/ast/expr/sexpr.rs
Isolated rule-body term classification and assignment parsing into a dedicated classification module.
  • Introduced src/parser/ast/rule/classification.rs containing parse_rule_body_term, assignment parsing, aggregation classification, for-loop body classification, and aggregation validation helpers.
  • Replaced inlined classification logic in RuleBodyExpression::classify in rule.rs with a call to classification::parse_rule_body_term, passing through spans and error tracking.
  • Relocated helper structs and functions (ClassificationContext, ForLoopComponents, aggregation_* helpers, multiple_aggregations_error, validate_aggregation) from rule.rs into the new module while preserving behavior.
src/parser/ast/rule.rs
src/parser/ast/rule/classification.rs
Split Pratt parser postfix handling into specialized submodules and rewired the main Pratt implementation.
  • Created new modules delay.rs, diff.rs, and postfix.rs under src/parser/expression/pratt/ to house delay, diff marker, and general postfix parsing logic.
  • Removed inline postfix-related methods (parse_postfix, delay/diff helpers, function call/bit-slice/dot-access parsing, parse_args) from pratt.rs and delegated to the new modules instead.
  • Updated pratt.rs module declarations to include the new submodules and adjusted imports (e.g., parse_u32_decimal, qualified callee helpers) into the appropriate new files.
src/parser/expression/pratt.rs
src/parser/expression/pratt/delay.rs
src/parser/expression/pratt/diff.rs
src/parser/expression/pratt/postfix.rs
Reworked expression parser tests to use a structured fixtures system rather than inline rstest cases.
  • Introduced src/parser/tests/expression/fixtures.rs plus submodules for valid and error fixtures (valid/basic.rs, valid/collections.rs, valid/control_flow.rs, valid/postfix.rs, valid/structured.rs, errors.rs).
  • Defined small case structs (ExpressionCase, CountedErrorCase, PostfixErrorCase, SpannedErrorCase) and helper functions (expression_cases, literal_cases, invalid_for_loop_sources, error_cases, postfix_error_cases, match_pattern_error_cases) to centralize test data.
  • Simplified src/parser/tests/expression.rs to iterate over fixture providers in plain #[test] functions instead of many #[rstest] parameterized cases, while preserving the same coverage and assertions.
src/parser/tests/expression.rs
src/parser/tests/expression/fixtures.rs
src/parser/tests/expression/fixtures/errors.rs
src/parser/tests/expression/fixtures/valid.rs
src/parser/tests/expression/fixtures/valid/basic.rs
src/parser/tests/expression/fixtures/valid/collections.rs
src/parser/tests/expression/fixtures/valid/control_flow.rs
src/parser/tests/expression/fixtures/valid/postfix.rs
src/parser/tests/expression/fixtures/valid/structured.rs
Performed minor parser and test infrastructure cleanups to align with the new module structure.
  • Adjusted imports in expression tests to use fixture functions and removed direct dependencies on low-level test_util AST constructors where not needed.
  • Ensured aggregation tracking and error spans continue to use literal_span consistently after refactor by threading spans into the classification helpers.
  • Documented new modules with brief comments clarifying their responsibility (rule classification, Expr S-expressions, test fixtures).
src/parser/tests/expression.rs
src/parser/ast/rule/classification.rs
src/parser/ast/expr/sexpr.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#223 Refactor src/parser/ast/rule.rs by moving rule-body term classification and aggregation tracking logic into a separate dedicated module so that rule.rs is smaller and focused on the public AST surface.
#223 Refactor src/parser/ast/expr.rs by extracting Expr S-expression formatting (to_sexpr and helpers) into its own module, keeping expr.rs focused on the core Expr definition.
#223 Refactor parser Pratt expression and tests by (a) splitting postfix/diff/delay handling code out of src/parser/expression/pratt.rs into focused submodules, and (b) moving the large inline test case tables from src/parser/tests/expression.rs into reusable fixtures modules.

Possibly linked issues


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

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 1, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/parser/tests/expression/fixtures/errors.rs

Comment on lines +217 to +232

fn collection_error_cases() -> Vec<CountedErrorCase> {
    vec![
        CountedErrorCase {
            src: "[1, 2",
            min_errs: 1,
        },
        CountedErrorCase {
            src: "{a: 1",
            min_errs: 1,
        },
        CountedErrorCase {
            src: "{a, b}",
            min_errs: 1,
        },
    ]
}

❌ New issue: Code Duplication
The module contains 4 functions with similar structure: collection_error_cases,control_flow_error_cases,operator_error_cases,postfix_counted_error_cases

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 1, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/parser/expression/pratt.rs

Comment on file

use chumsky::error::Simple;

use crate::parser::ast::{Expr, StringLiteral};
use crate::parser::span_utils::parse_u32_decimal;

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: Pratt.with_struct_literal_activation,Pratt.with_struct_literals_suspended

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

Share the setup and teardown flow used by the Pratt parser's struct
literal activation and suspension helpers. Keep the existing public helper
signatures and delegate their state changes through one private scope
method.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Default `CARGO` to the executable found on `PATH`, with a fallback to the
standard Rustup installation path under `HOME`. This lets hook environments
that omit the Cargo bin directory still run the repository Makefile targets.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Add a private helper for expression parser error cases that all expect a
single minimum error. Keep the existing case order and fixture function
signatures while removing repeated `CountedErrorCase` construction.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 1, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

src/parser/tests/expression/fixtures/errors.rs:

What lead to degradation?

The module contains 4 functions with similar structure: collection_error_cases,control_flow_error_cases,operator_error_cases,postfix_counted_error_cases

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

@leynos leynos marked this pull request as ready for review May 2, 2026 09:11
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.

Sorry @leynos, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai coderabbitai Bot added the Issue label May 2, 2026
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/parser/ast/rule/classification.rs`:
- Line 61: The check that treats missing assignment values uses
parts.value.is_empty(), which misses whitespace-only values like "x =   ";
update the logic to trim the string before checking emptiness (e.g., use
parts.value.trim().is_empty()) so that the code path in the classification logic
(where parts.value is inspected) treats whitespace-only assignments as missing
expressions and triggers the targeted diagnostic instead of falling through to a
generic parse error.

In `@src/parser/tests/expression/fixtures/valid/postfix.rs`:
- Around line 9-67: Add two happy-path fixtures to the
postfix_expression_cases() vec: one for the apostrophe diff postfix (e.g., src
"e'") and one for the delay postfix (e.g., src "f-<1>"), each as an
ExpressionCase entry; set their expected AST using the same test helper pattern
used elsewhere (e.g., postfix_diff(var("e")) and postfix_delay(var("f"),
lit_num("1")) or the equivalent helper names in the tests) so the postfix parser
branches exercised by src/parser/expression/pratt/postfix.rs (apostrophe diff
handling and -<...> delay handling) are asserted to succeed and cannot regress
silently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0ae55951-bb4d-44a2-a93e-b1901d4665ef

📥 Commits

Reviewing files that changed from the base of the PR and between 02e67a4 and 6d8c068.

📒 Files selected for processing (18)
  • Makefile
  • src/parser/ast/expr.rs
  • src/parser/ast/expr/sexpr.rs
  • src/parser/ast/rule.rs
  • src/parser/ast/rule/classification.rs
  • src/parser/expression/pratt.rs
  • src/parser/expression/pratt/delay.rs
  • src/parser/expression/pratt/diff.rs
  • src/parser/expression/pratt/postfix.rs
  • src/parser/tests/expression.rs
  • src/parser/tests/expression/fixtures.rs
  • src/parser/tests/expression/fixtures/errors.rs
  • src/parser/tests/expression/fixtures/valid.rs
  • src/parser/tests/expression/fixtures/valid/basic.rs
  • src/parser/tests/expression/fixtures/valid/collections.rs
  • src/parser/tests/expression/fixtures/valid/control_flow.rs
  • src/parser/tests/expression/fixtures/valid/postfix.rs
  • src/parser/tests/expression/fixtures/valid/structured.rs

Comment thread src/parser/ast/rule/classification.rs Outdated
Comment thread src/parser/tests/expression/fixtures/valid/postfix.rs Outdated
@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 3, 2026

@coderabbitai please create new GitHub issues for the following:

Out-of-scope: GitHub issues required

Docstring Coverage (⚠️ Warning)

Issue #229 ("Documentation gaps: missing module comments and inconsistent API doc sections") already tracks this. No new issue is needed — link the CodeScene warning to #229.

Developer Documentation (⚠️ Warning) and Snapshot Testing (⚠️ Warning)

These two are out of scope for this refactoring PR. Shall I open GitHub issues for:

  1. Create docs/developers-guide.md documenting the new module structure, responsibilities, and architectural boundaries introduced by this PR.
  2. Introduce snapshot testing for sexpr.rs (e.g. using insta) to guard the S-expression rendering output against regression.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create issue 1: Developer documentation
gh issue create \
  --repo leynos/ddlint \
  --title "Create docs/developers-guide.md documenting refactored parser module structure" \
  --assignee leynos \
  --body "## Background

PR `#259` refactored several internal parser modules below the 400-line maintainability guideline, introducing new modules with distinct responsibilities:

- \`src/parser/ast/expr/sexpr.rs\` — S-expression formatting for \`Expr\`
- \`src/parser/ast/rule/classification.rs\` — rule-body term classification
- \`src/parser/expression/pratt/delay.rs\` — delay-postfix parsing
- \`src/parser/expression/pratt/diff.rs\` — diff-marker postfix helpers
- \`src/parser/expression/pratt/postfix.rs\` — postfix-operator dispatch
- \`src/parser/tests/expression/fixtures/\` — structured test fixture hierarchy

No developer guide documents these modules, their responsibilities, or the architectural boundaries between them.

## Required work

Create \`docs/developers-guide.md\` covering:

1. The overall parser module layout and the 400-line size guideline.
2. Each new module: its responsibility, what it owns, and what it delegates.
3. Architectural boundaries (e.g. why sexpr formatting is separate from the AST definition; why classification is separate from rule parsing).
4. How to navigate the test fixture hierarchy when adding new expression parser tests.

## References

- PR `#259`: https://github.com/leynos/ddlint/pull/259
- Raised as out-of-scope in review comment: https://github.com/leynos/ddlint/pull/259
- Requested by `@leynos`"

# Create issue 2: Snapshot testing for sexpr.rs
gh issue create \
  --repo leynos/ddlint \
  --title "Introduce snapshot testing for Expr::to_sexpr() in sexpr.rs" \
  --assignee leynos \
  --body "## Background

PR `#259` extracted \`Expr::to_sexpr()\` into \`src/parser/ast/expr/sexpr.rs\`. The output is a text-based S-expression representation used in parser tests. Currently this output is validated only by inline string assertions; there are no snapshot tests to guard the rendering against regression.

## Required work

Introduce snapshot testing for \`src/parser/ast/expr/sexpr.rs\` using the [\`insta\`](https://insta.rs/) crate:

1. Add \`insta\` as a dev-dependency in \`Cargo.toml\`.
2. Write snapshot tests covering each \`Expr\` variant rendered by \`to_sexpr()\`, using \`insta::assert_snapshot!\`.
3. Commit the generated \`.snap\` files under \`src/parser/ast/expr/snapshots/\` (or the default \`insta\` snapshot directory).
4. Confirm \`cargo test\` and \`cargo insta review\` pass cleanly.

## Rationale

Snapshot tests make regressions in S-expression output immediately visible as diff noise in review, rather than requiring manual comparison of string literals scattered across test functions.

## References

- PR `#259`: https://github.com/leynos/ddlint/pull/259
- Raised as out-of-scope in review comment: https://github.com/leynos/ddlint/pull/259
- Requested by `@leynos`"

Length of output: 88


@leynos — both issues have been opened and assigned to you:

Both include backlinks to PR #259. The Docstring Coverage warning remains linked to the existing #229.

…ule docs

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot added Roadmap and removed Issue labels May 7, 2026
@leynos leynos changed the title Refactor large parser modules into smaller components under 400 lines Refactor large parser modules into smaller components under 400 lines (#223) May 19, 2026
leynos added 2 commits May 19, 2026 18:51
Add `docs/developers-guide.md` for the issue `#223` parser split and
update `docs/parser-implementation-notes.md` and `docs/contents.md` so
the ownership notes and index stay aligned with the current module
layout.
Document the extracted `pub(super)` parser helpers and add focused
module-local tests for the rule classifier and Pratt postfix boundaries.

Add inline `insta` snapshots for `Expr::to_sexpr()` so the extracted
S-expression formatter has snapshot coverage without introducing separate
snapshot fixture files.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Align the `AtomDiff` and `AtomDelay` helper docs with the postfix node
constructors used by parser fixtures.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Remove the extra comment above `postfix_expression_cases` so the fixture
module body matches the focused helper split requested for the large-method
cleanup.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot added Issue and removed Roadmap labels May 19, 2026
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.

♻️ Duplicate comments (1)
src/parser/expression/pratt/postfix.rs (1)

151-151: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Bind the discarded next_tok() result explicitly.

This comma-advance statement discards the Option return value without explicit binding. The other three call sites in this file now use let _ = self.ts.next_tok(); but this one was missed.

Proposed fix
-            self.ts.next_tok();
+            let _ = self.ts.next_tok();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parser/expression/pratt/postfix.rs` at line 151, Replace the lone call
that discards the Option returned from self.ts.next_tok() with an explicit
binding like the other call sites; locate the instance in
src/parser/expression/pratt/postfix.rs (inside the same function where other
next_tok() calls are used) and change the statement to bind the result (e.g.,
let _ = self.ts.next_tok();) so the Option is explicitly handled and the pattern
matches the other three call sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/parser/expression/pratt/postfix.rs`:
- Line 151: Replace the lone call that discards the Option returned from
self.ts.next_tok() with an explicit binding like the other call sites; locate
the instance in src/parser/expression/pratt/postfix.rs (inside the same function
where other next_tok() calls are used) and change the statement to bind the
result (e.g., let _ = self.ts.next_tok();) so the Option is explicitly handled
and the pattern matches the other three call sites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1cdb6394-c399-40ae-990b-71f8e787089c

📥 Commits

Reviewing files that changed from the base of the PR and between 7de8f93 and 841ef5f.

📒 Files selected for processing (11)
  • Cargo.toml
  • docs/contents.md
  • docs/developers-guide.md
  • docs/parser-implementation-notes.md
  • src/parser/ast/expr/sexpr.rs
  • src/parser/ast/rule/classification.rs
  • src/parser/expression/pratt/delay.rs
  • src/parser/expression/pratt/diff.rs
  • src/parser/expression/pratt/postfix.rs
  • src/parser/tests/expression/fixtures/valid/postfix.rs
  • src/test_util/expressions.rs

Make the comma consume in `parse_args` explicit by binding the returned
`Option`, matching the other intentional token-consumption sites in the
postfix parser.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot added Roadmap and removed Issue labels May 19, 2026
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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/parser/expression/pratt/postfix.rs`:
- Around line 171-188: The new tests miss exercising the diff and delay branches
in parse_postfix, so add unit tests that call parse_postfix (via
parse_expression) to cover the "'" (diff) postfix and the "-<" (delay) postfix,
including a test that sets up a pending diff and then ensures a delay is
rejected (verifying the guard that rejects delay while a diff is pending);
specifically add tests that assert the parsed AST or error messages for the "'"
and "-<" cases and one that confirms parse_postfix returns the expected error
when a delay is attempted while a diff is pending, referencing parse_postfix,
diff postfix, and delay postfix in your assertions.
- Around line 163-166: The module-wide Clippy suppression on the tests module is
too broad; remove the inner attribute `#![expect(clippy::expect_used, ...)]`
from the `mod tests` declaration and instead add item-level
`#[expect(clippy::expect_used, reason = "...")]` attributes to each test
function that calls `expect()`/`expect_err()` (for example
`qualified_callee_uses_call_postfix` and `trailing_argument_comma_is_rejected`)
so the lint is narrowly scoped to only those test items.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 79925021-05af-4cec-8d42-9af29b36a9d4

📥 Commits

Reviewing files that changed from the base of the PR and between 841ef5f and a0508e5.

📒 Files selected for processing (1)
  • src/parser/expression/pratt/postfix.rs

Comment thread src/parser/expression/pratt/postfix.rs Outdated
Comment thread src/parser/expression/pratt/postfix.rs
Add focused `parse_expression` tests for the Pratt postfix diff and delay
paths, including the pending-diff guard before delay parsing.

Narrow the `expect_used` Clippy expectations to the individual test cases
that need them instead of suppressing the whole test module.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot added Issue and removed Roadmap labels May 19, 2026
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/parser/expression/pratt/postfix.rs`:
- Around line 151-155: Capture the separator token's span before calling
self.ts.next_tok() and use that span when calling self.ts.push_error so the
diagnostic points at the comma instead of `)`/EOF; specifically, in the postfix
parsing code around the trailing-comma check, call something like let sep_span =
self.ts.peek_span().unwrap_or_else(|| self.ts.eof_span()) (or an equivalent that
inspects the separator) before invoking self.ts.next_tok(), then pass sep_span
into self.ts.push_error(sep_span, "unexpected trailing comma in argument list")
instead of using the later peek_span()/eof_span.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5d60a935-9967-447d-8e3e-02cc15596718

📥 Commits

Reviewing files that changed from the base of the PR and between a0508e5 and 1a6f9b8.

📒 Files selected for processing (1)
  • src/parser/expression/pratt/postfix.rs

Comment thread src/parser/expression/pratt/postfix.rs Outdated
Capture the comma span before consuming it in Pratt postfix argument
parsing, then use that span for the trailing-comma diagnostic.

Update the call parsing regression expectation so it verifies the diagnostic
now points at the comma rather than the closing parenthesis.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Capture consumed postfix separator spans explicitly and report trailing
argument commas at the comma token. Remove the defensive diff-marker EOF
fallback so the helper exposes the fallible token consume directly.

Strengthen parser tests by checking the wrapped diff expression,
assignment pattern and value details, and S-expression rendering beside
snapshots.
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

[High] Oversized modules violate local maintainability guideline (400-line cap)

1 participant