Skip to content

JavaScript: Fix compactChildNodes dropping node list fields - #4187

Merged
Earlopain merged 1 commit into
ruby:mainfrom
marcoroth:fix-js-compact-child-nodes
Jul 29, 2026
Merged

JavaScript: Fix compactChildNodes dropping node list fields#4187
Earlopain merged 1 commit into
ruby:mainfrom
marcoroth:fix-js-compact-child-nodes

Conversation

@marcoroth

Copy link
Copy Markdown
Contributor

In the JavaScript bindings compactChildNodes() is generated two ways. When a node has at least one OptionalNodeField it builds up a compact array, otherwise it returns an array literal.

The literal branch spreads node lists correctly, but the compact branch used concat and threw the result away:

compactChildNodes() {
  const compact = [];

  compact.concat(this.conditions); // <- return value discarded

  if (this.statements) {
    compact.push(this.statements);
  }

  return compact;
}

BasicVisitor#visitChildNodes walks compactChildNodes(), so a Visitor silently never descends into those children:

class Collector extends Visitor {
  found = []

  visitInstanceVariableReadNode(node) { 
    this.found.push(node.name) 
  }
}

collect("if a\n  @foo\nend")            // => ["@foo"]
collect("case a\nwhen 1\n  @foo\nend")  // => []
collect("@foo, @bar = 1, 2")            // => []
collect("def call(a = @foo); end")      // => []

This affects 17 fields across 11 node types:

  • ArrayPatternNode
  • BlockParametersNode
  • CaseMatchNode
  • CaseNode
  • FindPatternNode
  • HashPatternNode
  • MultiTargetNode
  • MultiWriteNode
  • ParametersNode
  • RescueNode and
  • WhenNode

Nodes without an optional field take the literal branch and are fine, which is why StatementsNode works and the problem is easy to miss.

marcoroth added a commit to marcoroth/herb that referenced this pull request Jul 29, 2026
`@ruby/prism` generates `compactChildNodes()` with
`compact.concat(this.field)` and discards the result, so node list
fields are dropped from the traversal. Visitors never descended into
`when`/`in` branch conditions, multiple assignment targets, `rescue`
exceptions or method parameters.

This pull request overrides the Prism visitors and walks `childNodes()`
instead, which spreads those arrays correctly.

This fixes false negatives in `erb-no-instance-variables-in-partials`,
`erb-no-debug-output` and `erb-no-unused-literals`.

Upstream fix: ruby/prism#4187
@Earlopain

Copy link
Copy Markdown
Collaborator

I think this was closed by accident?

@marcoroth marcoroth reopened this Jul 29, 2026
@marcoroth

Copy link
Copy Markdown
Contributor Author

lol whoops, I didn't realize it would close the issue here in ruby/prism. Thanks for the heads up @Earlopain! 🙏🏼

@Earlopain Earlopain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks! Would have worked with ruby semantics 😉

@Earlopain
Earlopain merged commit c36a8fa into ruby:main Jul 29, 2026
200 of 202 checks passed
@marcoroth
marcoroth deleted the fix-js-compact-child-nodes branch July 29, 2026 19:40
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.

2 participants