Parser: Introduce two-pass algorithm for matching HTML Elements#795
Merged
Conversation
e108605 to
74467cc
Compare
This pull request introduces a two-pass algorithm for HTML tag matching to better handle ERB control flow boundaries.
Previously, HTML tags were matched during parsing (single-pass), which caused issues when tags appeared across ERB control flow boundaries:
```erb
<div>
<% if true %>
<h1>Title
<% end %>
</h1>
</div>
```
This would incorrectly report errors or miss mismatched tags because the parser couldn't understand that the `<% if %>` creates a scope boundary.
---
In the first pass during parsing, the parser collects `HTMLOpenTagNode` and `HTMLCloseTagNode` nodes separately without attempting to match them or perform any validation.
In the second pass after the ERB structure has been fully analyzed, the new `herb_parser_match_html_tags_post_analyze()` function matches tags while respecting ERB control flow scope boundaries.
This function recursively processes all ERB control structures including `if`/`elsif`/`else`, `case`/`when`, `begin`/`rescue`/`ensure`, and others, attaching any mismatch errors directly to the relevant stray open or close tag
nodes.
74467cc to
59c08c3
Compare
This was referenced Nov 7, 2025
marcoroth
added a commit
that referenced
this pull request
Nov 11, 2025
This pull request fixes a critical performance regression in the two-pass tag matching algorithm introduced in #795. Documents that previously parsed in ~75ms were taking ~11 seconds (and in some cases didn't even complete in a reasonable timeframe, see #828). The `match_tags_visitor` function was causing double recursion by manually processing array fields through calls to `match_tags_in_node_array()`, but then returning `true` which instructed `herb_visit_node` to also automatically traverse those same children a second time. This created an exponential explosion where each level of nesting squared the number of visits. Resolves #828
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces a two-pass algorithm for HTML tag matching to better handle ERB control flow boundaries and provide more accurate and actionable error messages.
Previously, HTML tags were matched during parsing (single-pass), which caused issues when tags appeared across ERB control flow boundaries. This would incorrectly report errors or miss mismatched tags because the parser couldn't understand that the
<% if %>creates a scope boundary.Now with this pull request:
In the first pass during parsing, the parser collects
HTMLOpenTagNodeandHTMLCloseTagNodenodes separately without attempting to match them or perform any validation, so it doesn't built anyHTMLElementNodeanymore during this pass.In the second pass after the ERB structure has been fully analyzed, the new
herb_parser_match_html_tags_post_analyze()function matches tags while respecting ERB control flow scope boundaries.This function recursively processes all ERB control structures including
if/elsif/else,case/when,begin/rescue/ensure, and others, attaching any mismatch errors directly to the relevant stray open or close tag nodes.Improved HTML Element Matching
Before

After
ERB Control Error Improvements
Before
After