Skip to content

perf: add fast string pre-checks to skip unnecessary parsing#456

Open
ryanquanz wants to merge 1 commit into
mainfrom
perf/fast-string-prechecks
Open

perf: add fast string pre-checks to skip unnecessary parsing#456
ryanquanz wants to merge 1 commit into
mainfrom
perf/fast-string-prechecks

Conversation

@ryanquanz
Copy link
Copy Markdown

Summary

Add cheap string checks before expensive RubyNode.parse and Tag.from_node calls in several linters.

Problem

Several linters parse every ERB snippet with BetterHtml::TestHelper::RubyNode.parse or create BetterHtml::Tree::Tag objects for every HTML tag, even when the vast majority could not possibly match. For example, NoJavascriptTagHelper parses every ERB snippet looking for javascript_tag calls — but in a typical codebase, >99% of snippets don't contain that string.

Similarly, AllowedScriptType, RequireScriptNonce, and RequireInputAutocomplete create full Tag objects for every HTML tag even though they only care about <script> or <input> tags specifically.

Changes

4 files changed, +26 −1. All additive — only adds next unless guards before existing logic:

  • NoJavascriptTagHelper: next unless source.include?("javascript_tag")
  • RequireInputAutocomplete: next unless source.match?(FORM_HELPER_NAMES_PATTERN) for ERB helpers; next unless tag_name == "input" for HTML tags
  • RequireScriptNonce: next unless source.match?(TAG_HELPER_PATTERN) for ERB helpers; next unless tag_name == "script" for HTML tags
  • AllowedScriptType: next unless tag_name == "script" before Tag.from_node

The tag name is read directly from the AST node (tag_node.to_a[1].loc.source) which avoids creating the full Tag object.

Impact

The improvement scales with the ratio of non-matching to matching tags/snippets. In a typical codebase with thousands of ERB snippets and HTML tags but very few javascript_tag calls or <script> tags, this avoids the vast majority of parsing work in these linters.

No new tests needed — existing specs cover both "offense found" (guard passes, full logic runs) and "no offense" (guard filters correctly) paths.

Add cheap string checks before expensive RubyNode.parse and
Tag.from_node calls in several linters:

- NoJavascriptTagHelper: skip parsing unless source contains
  'javascript_tag'
- RequireInputAutocomplete: skip parsing unless source matches a
  form helper name pattern; skip Tag.from_node for non-input tags
- RequireScriptNonce: skip parsing unless source matches a tag
  helper pattern; skip Tag.from_node for non-script tags
- AllowedScriptType: skip Tag.from_node for non-script tags

These linters previously parsed every ERB snippet or created Tag
objects for every HTML tag, even when the vast majority could not
possibly match.
@ryanquanz ryanquanz force-pushed the perf/fast-string-prechecks branch from 0b5e1df to 5b1f863 Compare March 30, 2026 14:24
@ryanquanz ryanquanz marked this pull request as ready for review March 30, 2026 14:46
@ryanquanz ryanquanz requested a review from etiennebarrie March 31, 2026 20: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