Skip to content

Wire prism_nodes injection for the remaining 10 prism-dependent rules (herb-embedded-ada) - #47

Merged
jleo3 merged 1 commit into
mainfrom
herb-embedded-ada
Aug 18, 2026
Merged

Wire prism_nodes injection for the remaining 10 prism-dependent rules (herb-embedded-ada)#47
jleo3 merged 1 commit into
mainfrom
herb-embedded-ada

Conversation

@jleo3

@jleo3 jleo3 commented Aug 18, 2026

Copy link
Copy Markdown
Member

Summary

  • ResultEnvelope.parse now handles prism_nodes/prism_nodes_deep the same way gu7 handled prism_program: never forward the flag to Herb.parse itself (raw ASCII-8BIT bytes break .to_json), but compute the Prism bytes separately as a JSON-safe Integer array and inject them — one Prism parse per AST_ERB_* node, scoped to that node's own embedded-Ruby content but still offset-correct against the whole file (blank every byte outside the node's content range, preserving newlines, then re-dump).
  • Ruby's Prism.dump has no API to serialize an arbitrary sub-node, so injected bytes always deserialize to a ProgramNode — but every prism_nodes-dependent rule expects prismNode to be the single embedded expression node directly (e.g. isAssignmentNode checks prismNode.constructor.name). js/ruby_backend.js patches every ERB*Node's prismNode getter, once, to unwrap a single-statement ProgramNode down to its inner node — a scoped patch in our own integration file, not the vendored bundle.
  • CHARTER.md's prism_program/prism_nodes note (from gu7) is rewritten to document both mechanisms now being wired.

Acceptance criteria

  • All 10 listed rules produce real, correct diagnostics through Bridge#lint — manually verified offense-for-offense against the real reference linter (npx @herb-tools/linter) for all 10 (a11y-no-autofocus-attribute, actionview-no-silent-helper, actionview-no-unnecessary-tag-attributes, erb-no-output-in-attribute-position, erb-no-silent-statement, erb-no-unsafe-raw, erb-no-unsafe-script-interpolation, erb-no-unused-expressions, erb-no-unused-literals, erb-prefer-direct-output), including rules whose offense location is derived from prismNode's own byte offsets. Two of these (erb-no-unsafe-raw, erb-no-silent-statement) get automated end-to-end coverage in spec/bridge_lint_spec.rb.
  • The mechanism does not reintroduce the JSON::GeneratorError bug — FORWARDABLE_OPTIONS still excludes prism_nodes/prism_nodes_deep from Herb.parse itself; injected bytes are always a plain Integer array. New specs in spec/result_envelope_spec.rb assert JSON-safety and shape for both prism_nodes and prism_nodes_deep.
  • CHARTER.md updated to describe the implemented mechanism, which node types carry injected bytes, how offsets are computed, and why/where the JS-side unwrap patch lives.
  • bundle exec rake passes locally (105 examples, 0 failures, no RuboCop offenses).

Test plan

  • bundle exec rake (RuboCop + full RSpec suite) passes locally.
  • Manually verified all 10 target rules produce offense-for-offense matching diagnostics against npx @herb-tools/linter on hand-built fixtures, plus negative cases (assignments correctly skipped, plain output correctly not flagged, mutation calls correctly excluded).

🤖 Generated with Claude Code

… (herb-embedded-ada)

ResultEnvelope.parse now handles prism_nodes/prism_nodes_deep the same way
gu7 handled prism_program: never forward the flag to Herb.parse itself
(which returns raw ASCII-8BIT bytes and breaks .to_json), but compute the
Prism bytes separately as a JSON-safe Integer array and inject them.

Unlike prism_program's single whole-document parse, prism_nodes needs one
Prism parse per AST_ERB_* node, scoped to just that node's own embedded-Ruby
content token but still offset-correct against the whole file: every byte
outside the node's content range gets blanked (preserving newlines) before
re-dumping, so the resulting parse's only real statement is that node's own
expression, at its true position in the file.

Ruby's Prism.dump has no API to serialize an arbitrary sub-node, so the
injected bytes always deserialize to a ProgramNode - but every
prism_nodes-dependent rule expects prismNode to be the single embedded
expression node directly (e.g. isAssignmentNode checks
prismNode.constructor.name). js/ruby_backend.js patches every ERB*Node's
prismNode getter, once, to unwrap a single-statement ProgramNode down to its
inner node, rather than touching the vendored bundle.

Manually verified all 10 target rules (a11y-no-autofocus-attribute,
actionview-no-silent-helper, actionview-no-unnecessary-tag-attributes,
erb-no-output-in-attribute-position, erb-no-silent-statement,
erb-no-unsafe-raw, erb-no-unsafe-script-interpolation,
erb-no-unused-expressions, erb-no-unused-literals, erb-prefer-direct-output)
produce offense-for-offense matching diagnostics against the real reference
linter, including offset-derived locations.
@jleo3
jleo3 merged commit 95408a0 into main Aug 18, 2026
1 check passed
@jleo3
jleo3 deleted the herb-embedded-ada branch August 18, 2026 15:44
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.

1 participant