Skip to content

build(deps)!: upgrade htmlparser2 to v12 - #22

Merged
Kikobeats merged 3 commits into
masterfrom
Kikobeats/htmlparser2-v12
Oct 28, 2026
Merged

Kikobeats merged 3 commits into
masterfrom
Kikobeats/htmlparser2-v12

Conversation

@Kikobeats

@Kikobeats Kikobeats commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Supersedes #20 (Dependabot bump to htmlparser2 12.0.0, which failed test).

Why #20 failed

  • v12 tokenizes foreign content itself via parser.isInForeignContext(). stateBeforeSpecialS/T are gone, so our ForeignTokenizer subclass was dead code (that's the uncovered lines 17-32 in the failing run).
  • v12 always treats annotation-xml as an HTML integration point. Without encoding="text/html" that's wrong per spec, so <math><annotation-xml><script src="/a.js"/>… swallowed the following scripts.

What changed

src/index.js:

  • Drops the Tokenizer subclass and the require.resolve('htmlparser2')/Tokenizer.js path hack. isInForeignContext now points at our own namespace stack (html | svg | math).
  • Removes the workarounds v12 now covers: the <script/> end search (findScriptEnd/scriptEnd), isInScript, and iframe/noembed/noframes/plaintext raw-text tracking.
  • noscript (htmlparser2 has no raw-text state for it) is skipped by jumping the tokenizer to its </noscript. v12's new raw-text tags could otherwise swallow the end tag.
  • Integration points are namespace-aware (desc/title/foreignObject only in SVG, mi/mo/mn/ms/mtext and html annotation-xml only in MathML). They're pushed after htmlparser2 decides on />, so <math><title/> self-closes.
  • Foreign content breakout (<p>, <div>, <font color>, …) per spec.
  • <template> inside svg/math isn't inert.
  • svg/math nested in foreign content keep the parent namespace.
  • A <script cut off at the end of the input is no longer reported.

package.json: htmlparser2 ~12.0.0, engines.node >= 20.19.

Breaking change

htmlparser2 v11+ needs Node.js >= 20.19 (ESM-only, loaded through require(esm)). The commit is marked !, so standard-version will cut 2.0.0.

How it was tested

  • npm test: 40 tests, 100% coverage. 6 new tests, and each one fails on the intermediate version that lacked its fix.

  • Differential fuzz against parse5 (WHATWG spec, scripting on), 50k random inputs per seed, 3 seeds:

    spec mismatches
    master (v10) ~3,500 / 50k (~7%)
    this PR (v12) 19-29 / 50k (~0.05%)

    The remaining mismatches are htmlparser2's simplified element stack (</div>/</template> popping elements the spec keeps). Master has them too.

  • Interleaved A/B benchmark against master: 1MB page −0.9%, 20KB page −1.1% (parity). Bare htmlparser2 v10 and v12 perform the same.

  • Cold adversarial review (separate agent, parse5 oracle, about 190 hand-written cases + 160k fuzz inputs) found one regression (svg inside math). It's fixed and covered by a test.

Known coupling

The noscript skip writes parser.tokenizer.index, a private field. That's the same level of internals the old code relied on. The ~12.0.0 pin plus the noscript tests catch any change in a future release.

🤖 Generated with Claude Code


Note

Medium Risk
Large rewrite of script-extraction parsing (SVG/MathML/noscript) on a security-sensitive path, plus a breaking Node engine bump and coupling to htmlparser2’s private tokenizer index for noscript skipping.

Overview
Upgrades htmlparser2 from v10 to v12 and raises the minimum Node.js version to 20.19 (breaking release).

The parser no longer subclasses htmlparser2’s tokenizer. Instead it drives parser.isInForeignContext from an internal HTML/SVG/MathML namespace stack, and drops older workarounds (findScriptEnd, manual raw-text tracking, the ForeignTokenizer hack).

Script discovery is aligned more closely with HTML foreign-content rules: namespace-aware integration points (title/desc in SVG, mi/mo/… and conditional annotation-xml in MathML), breakout when HTML tags like div or font color appear inside SVG/MathML, noscript handling by skipping to </noscript> in HTML (via tokenizer index), non-inert <template> inside SVG/MathML, and incomplete <script tags at EOF are ignored. Raw-text hiding in tests now also covers style, textarea, title, and xmp.

Tests add broad coverage for these edge cases (noscript, breakout tags, mglyph/malignmark, nested svg/math, truncated scripts).

Reviewed by Cursor Bugbot for commit c523c07. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved parsing of SVG and MathML content, including namespace transitions, integration points, and tags that return parsing to HTML.
    • Corrected handling of noscript content, templates, script attributes, and incomplete script tags at the end of input.
    • Expanded coverage for raw-text elements such as style, textarea, title, and xmp, as well as additional SVG and MathML edge cases.
  • Compatibility
    • The minimum supported Node.js version is now 20.19.

htmlparser2 v12 tokenizes foreign content itself through
`isInForeignContext`, so the Tokenizer subclass no longer ran. The parser
now points that hook at our own namespace stack, and the workarounds v12
makes redundant (self-closing script end search, iframe/noembed/noframes/
plaintext raw text tracking, isInScript) are gone.

v12 always treats annotation-xml as an integration point and makes more
tags raw text, which broke annotation-xml without an HTML encoding and let
raw text tags swallow </noscript>. Fixed by:

- skipping noscript content up to its end tag, as the spec raw text state does
- namespace-aware integration points, pushed after the self-closing decision
- foreign content breakout (p, div, font[color], ...)
- template inside svg/math is not inert
- svg/math nested in foreign content keep the parent namespace
- scripts cut off at the end of the input are not reported

Differential fuzz against parse5 (WHATWG spec): spec mismatches go from
~7% of random inputs to ~0.05%. Benchmark is at parity with master.

BREAKING CHANGE: htmlparser2 v11+ requires Node.js >= 20.19.

Closes #20

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 98797baf-fe62-46b9-a06a-9e394448b276
Commits

Reviewing files that changed from the base of the PR and between b0fc2e5 and c523c07.

Files selected for processing: 2
  • src/index.js
  • test/index.js

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The parser replaces custom tokenization and raw-text tracking with namespace and template state for HTML, SVG, and MathML. It handles integration points, foreign-content breakouts, and HTML noscript content. Tests cover these behaviors and incomplete script tags.

Changes: Parser namespace handling

Layer / File(s) Summary
Namespace model and parser requirements
package.json, src/index.js
The htmlparser2 requirement changes to ~12.0.0, and the minimum Node.js version changes to >= 20.19. The parser defines namespace constants and sets for integration points, breakouts, font attributes, and noscript end-tag detection.
Namespace-aware parsing and validation
src/index.js, test/index.js
Parser callbacks track namespaces and template depth. Script attributes are collected outside HTML templates. HTML noscript content is skipped through its closing tag, while foreign-content noscript is parsed normally. Tests cover raw-text elements, integration points, breakouts, templates, nested namespaces, and incomplete script tags.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Other

Sequence Diagram(s)

sequenceDiagram
  participant HTMLSource
  participant htmlparser2
  participant NamespaceAndTemplateState
  participant ScriptAttributeCollection
  HTMLSource->>htmlparser2: parse input
  htmlparser2->>NamespaceAndTemplateState: pass tag callbacks
  NamespaceAndTemplateState-->>htmlparser2: provide namespace and template context
  htmlparser2->>ScriptAttributeCollection: collect script attributes outside HTML templates
Loading

Possibly related PRs

  • Kikobeats/html-scripts#18: Introduced the htmlparser2 integration and foreign-content behavior that this PR revises for v12.
Merge Risk: Minimal · up to c523c

No actionable issue remains from this review; the PR is mergeable after normal checks.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the primary change: the breaking upgrade of htmlparser2 to version 12. It is concise and related to the dependency, parser, and Node.js changes.
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 2 files.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

html-scripts

Details
Benchmark suite Current: c523c07 Previous: 114f48b Ratio
getScripts 1MB page 54.8 ms (54.1-56.3) 90.4 ms (89.7-98.4) 0.61
getScripts 20KB page 72.8 ms (71.8-77.5) 116.6 ms (116.5-120.0) 0.62

This comment was automatically generated by workflow using github-action-benchmark.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b0fc2e5. Configure here.

Comment thread src/index.js Resolved
Kikobeats and others added 2 commits October 28, 2026 08:56
The tree construction dispatcher inserts an svg start tag in the SVG
namespace when the current node is a MathML annotation-xml, whatever its
encoding. Track open element depth so only a direct child gets this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Those two elements stay in MathML when the current node is a text integration point. Treating them as HTML let a script there swallow later scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Kikobeats
Kikobeats merged commit cae34d8 into master Oct 28, 2026
5 checks passed
@Kikobeats
Kikobeats deleted the Kikobeats/htmlparser2-v12 branch October 28, 2026 09:04
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