Skip to content

refactor(parser): extract fill_heredoc_contents visitor helpers#68

Merged
mpecan merged 1 commit into
mainfrom
feat/57-heredoc-visitor
Apr 17, 2026
Merged

refactor(parser): extract fill_heredoc_contents visitor helpers#68
mpecan merged 1 commit into
mainfrom
feat/57-heredoc-visitor

Conversation

@mpecan
Copy link
Copy Markdown
Owner

@mpecan mpecan commented Apr 17, 2026

Summary

Refactors fill_heredoc_contents in src/parser/helpers.rs — the ~100-line recursive AST visitor with 12 arms (gated by #[allow(clippy::too_many_lines, clippy::match_same_arms)]) becomes a ~60-line dispatcher that delegates to per-category helpers:

  • fill_each — generic walk over a &mut [Node] slice; absorbs every redirect loop AND Pipeline's command-walk.
  • fill_command — Command (assignments + words + redirects).
  • fill_if — If (condition + then + optional else + redirects). else_body: Option<&mut Node> via as_deref_mut().
  • fill_body_and_redirects — Subshell / BraceGroup / For / Select (fused into one OR-pattern arm — all four have body + redirects).
  • fill_cond_body_redirects — While / Until (condition + body + redirects).
  • fill_case — Case (walk each pattern's optional body + redirects).

Both #[allow] attributes dropped; no remaining arms are textually identical and the function fits under the 60-line clippy cap.

Test coverage

Added 3 parable test cases in tests/parable/14_here_documents.tests to pin future regression prevention — these paths weren't directly exercised before:

  • heredoc in if else branch — covers else_body.as_deref_mut().
  • heredoc in case pattern body — covers fill_case's inner loop.
  • heredoc nested while inside if else — covers multi-level recursion through the lexer's heredoc queue.

Test plan

  • cargo fmt
  • cargo clippy --all-targets -- -D warnings — no warnings
  • cargo test — 252 passed (56 heredoc cases incl. 3 new ones)
  • Oracle suite — 12 oracle_* tests pass (cargo test --test integration oracle_)
  • AST output is byte-identical (constitutional: compatibility is correctness)

Stack

Part of the v0.2.0 refactoring cycle (#61). This is PR 7 of 10.

Closes #57

🤖 Generated with Claude Code

fill_heredoc_contents in src/parser/helpers.rs was a ~100-line recursive
AST visitor with ~12 arms, gated by #[allow(clippy::too_many_lines,
clippy::match_same_arms)]. Many arms shared boilerplate:

- The `for r in redirects { fill_heredoc_contents(r, lexer); }` loop
  appeared in 7 arms.
- Subshell, BraceGroup, For, and Select all follow the body+redirects
  shape but were split across two match arms.

The refactor introduces six module-private helpers:

- fill_each: generic walk over a node slice (absorbs redirect-walks and
  Pipeline's command-walk)
- fill_command: Command arm
- fill_if: If arm (takes Option<&mut Node> for else_body via
  as_deref_mut())
- fill_body_and_redirects: Subshell / BraceGroup / For / Select (one
  OR-pattern arm)
- fill_cond_body_redirects: While / Until
- fill_case: Case arm

Dispatcher shrinks to ~60 lines. Both #[allow] attributes are dropped —
the remaining arms are textually distinct and the function now fits
under the 60-line clippy cap.

Also adds 3 parable test cases in tests/parable/14_here_documents.tests
to pin behavior against future visitor edits:

- heredoc in if else branch (exercises else_body.as_deref_mut())
- heredoc in case pattern body (exercises fill_case)
- heredoc nested while inside if else (exercises multi-level recursion
  through the lexer heredoc queue)

AST output is byte-identical; all 252 unit/integration + 12 oracle tests
pass. Part of #61 (v0.2.0 cycle).

Closes #57

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mpecan mpecan merged commit 40e6165 into main Apr 17, 2026
5 checks passed
@mpecan mpecan deleted the feat/57-heredoc-visitor branch April 17, 2026 13:55
mpecan pushed a commit that referenced this pull request Apr 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.2.0](rable-v0.1.15...rable-v0.2.0)
(2026-04-18)


### ⚠ BREAKING CHANGES

* tighten lexer API surface and relocate WordSpan to ast
([#70](#70))

### Bug Fixes

* **format:** align cmdsub reformatter with bash canonical form
([#49](#49))
([c7a4411](c7a4411))
* **lexer:** accept sloppy heredoc terminator in cmdsub mode
([#50](#50))
([40f394f](40f394f))
* **lexer:** backticks opaque when content is invalid
([#71](#71))
([e72166f](e72166f)),
closes [#38](#38)
* **lexer:** disable reserved-word recognition after assignment words
([#44](#44))
([42e1fc0](42e1fc0))
* **lexer:** stop treating ]] and unbalanced [...] as special outside
conditionals ([#45](#45))
([4bf5a5c](4bf5a5c))
* **parser:** fall back from (( … )) arith to nested subshells
([#48](#48))
([1437f00](1437f00))


### Code Refactoring

* **format:** introduce Formatter struct
([#65](#65))
([d965a8f](d965a8f))
* **lexer:** drop Result&lt;Token&gt; wrapper from operator readers
([#62](#62))
([d52a841](d52a841))
* **lexer:** split read_word_token into classify + advance + dispatch
helpers ([#63](#63))
([3ba09f5](3ba09f5))
* **parser:** extract fill_heredoc_contents visitor helpers
([#68](#68))
([40e6165](40e6165))
* **parser:** extract helpers from three oversize parsers
([#69](#69))
([25d0762](25d0762))
* **sexp:** dispatch NodeKind Display to per-category helpers
([#66](#66))
([44b0330](44b0330))
* **sexp:** table-drive ANSI-C escape dispatch
([#67](#67))
([91a5267](91a5267))
* tighten lexer API surface and relocate WordSpan to ast
([#70](#70))
([5171d01](5171d01))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: repository-butler[bot] <166800726+repository-butler[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PR 7: parser/helpers.rs — fill_heredoc_contents visitor

1 participant