Skip to content

Parser: Treat javascript_tag content as foreign content#1434

Merged
marcoroth merged 2 commits intomainfrom
javascript-tag-content
Mar 20, 2026
Merged

Parser: Treat javascript_tag content as foreign content#1434
marcoroth merged 2 commits intomainfrom
javascript-tag-content

Conversation

@marcoroth
Copy link
Owner

@marcoroth marcoroth commented Mar 20, 2026

This pull request fixes how the parser handles javascript_tag do blocks when the body contains HTML-like syntax such as < operators in JavaScript expressions (e.g. n <o.length, for (let i = 0; i<items.length; i++)).

Previously, the outer HTML parser would interpret < inside a javascript_tag do body as an HTML tag opening, creating a broken HTMLOpenTagNode that swallowed the <% end %> closing tag. This caused a null dereference in the analyzer when it tried to access block_node->end_node.

The fix has two parts:

  1. html parser option and foreign content re-parsing

    A new html parser option (defaults to true) controls whether the parser runs in HTML mode or foreign content mode. When html: false, the parser treats everything as literal text and ERB tags, no HTML tag parsing. The analyzer now re-parses javascript_tag do body content with html: false to produce clean LiteralNodes, regardless of what the outer HTML parse produced.

    New start_line and start_column options allow the re-parsed body to carry correct source locations from the original template.

  2. Swallowed <% end %> recovery

    When the outer HTML parse swallows <% end %> inside a broken HTMLOpenTagNode, the analyzer walks the AST to find the swallowed ERBContentNode with end content. It creates a proper ERBEndNode from it and uses it as the close_tag, with stale ERBControlFlowScopeError cleared.

For example, the following template with action_view_helpers: true:

<%= javascript_tag do %>
  n <o.length
<% end %>

Now correctly produces:

@ DocumentNode (location: (1:0)-(3:9))
└── children: (1 item)
    └── @ HTMLElementNode (location: (1:0)-(3:9))
        ├── open_tag:
           └── @ ERBOpenTagNode (location: (1:0)-(1:24))
               ├── tag_opening: "<%=" (location: (1:0)-(1:3))├── content: " javascript_tag do " (location: (1:3)-(1:22))
               ├── tag_closing: "%>" (location: (1:22)-(1:24))├── tag_name: "script" (location: (1:4)-(1:18))
               └── children: []
        
        ├── tag_name: "script" (location: (1:4)-(1:18))
        ├── body: (1 item)
           └── @ LiteralNode (location: (1:24)-(3:0))
               └── content: "\n  n <o.length\n"
        
        ├── close_tag:
           └── @ ERBEndNode (location: (3:0)-(3:9))
               ├── tag_opening: "<%" (location: (3:0)-(3:2))├── content: " end " (location: (3:2)-(3:7))
               └── tag_closing: "%>" (location: (3:7)-(3:9))
        
        ├── is_void: false
        └── element_source: "ActionView::Helpers::JavaScriptHelper#javascript_tag"

Resolves #1426

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 20, 2026

npx https://pkg.pr.new/@herb-tools/formatter@1434
npx https://pkg.pr.new/@herb-tools/language-server@1434
npx https://pkg.pr.new/@herb-tools/linter@1434

commit: 8885aef

@github-actions
Copy link

github-actions bot commented Mar 20, 2026

🌿 Interactive Playground and Documentation Preview

A preview deployment has been built for this pull request. Try out the changes live in the interactive playground:


🌱 Grown from commit 8885aef


✅ Preview deployment has been cleaned up.

@marcoroth marcoroth force-pushed the javascript-tag-content branch from 7187710 to be60bfb Compare March 20, 2026 15:39
@github-actions github-actions bot added the lexer label Mar 20, 2026
@marcoroth marcoroth merged commit 7527a66 into main Mar 20, 2026
32 checks passed
@marcoroth marcoroth added this to the v1.0.0 milestone Mar 20, 2026
@marcoroth marcoroth deleted the javascript-tag-content branch March 20, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parser: HTML-like content in javascript_tag helper

1 participant