Skip to content

fix(parser): don't treat an HTML void name in an svg or math namespace as void - #667

Merged
Brooooooklyn merged 3 commits into
voidzero-dev:mainfrom
GabeSilvaDev:fix/svg-void-end-tag
Nov 2, 2026
Merged

Brooooooklyn merged 3 commits into
voidzero-dev:mainfrom
GabeSilvaDev:fix/svg-void-end-tag

Conversation

@GabeSilvaDev

@GabeSilvaDev GabeSilvaDev commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

<svg><area></area></svg> failed to compile with Void elements do not have end tags "area", and <svg><area>{{ x }}</area></svg> lost the text inside area. ngc 22.2 accepts both.

Angular names an element without a prefix in its parent's namespace (_getElementFullName), so <area> inside <svg> is :svg:area. getHtmlTagDefinition(':svg:area') is the default definition, which is not void. The HTML parser here looked the definition up by the local name only, area, both when it opened the element and when it checked the end tag.

Changes

  • HtmlParser::inherits_namespace walks the open elements from the closest one, skipping blocks. An element passes down its own prefix (svg:g), its implicit one (svg, math) or the one it took from its parent. foreignObject passes nothing down, as preventNamespaceInheritance does upstream.
  • consume_element_start only marks an element void when it is in no namespace, so :svg:area keeps its children and its end tag.
  • consume_element_end only reports Void elements do not have end tags for an element in no namespace.

The namespaces themselves are still resolved after parsing, as before. With the fix, the full output for <svg><area>{{ x }}</area><br></br><foreignObject><area><b>y</b></foreignObject></svg> has the same instructions as ngtsc 22.2.3 (with NO_ERRORS_SCHEMA, since ngtsc's schema check reports NG8001 for :svg:area).

Not in this PR

<div/> gives no Only void, custom and foreign elements can be self closed error here. Upstream marks every known DOM element canSelfClose: false (html_tags.ts, from DomElementSchemaRegistry.allKnownElementNames()), and tags.rs has no such list, so every unknown name falls back to canSelfClose: true. That is a separate gap.

Checks

  • New foreign_void_elements tests in html_parser_test.rs, each checked against ngc 22.2 parseTemplate: <svg><area></area>, <svg><br></br>, a nested <g>, <math><br></br>, an @if block between svg and area, <svg:area></svg:area> and <svg:g><area></area></svg:g> parse without errors; <svg><area>t</area> keeps the text; area inside foreignObject, and at the top level, still reports the end tag.
  • Removing the foreignObject stop, the block skip, the explicit prefix check, or the namespace check at the start or at the end tag each fails a test.
  • cargo test --all-features, cargo fmt --all -- --check and conformance (1264/1264, snapshot unchanged) pass.

Review follow-up (9538418, d96e722)

A sweep of ~220 templates against Angular 22.2 (HtmlParser tree and ngtsc output) found no regression. It did find places that still read area as void once the element is open:

template                              Angular 22.2                       before       now
<svg><area><g></g></area></svg>        area > g                           ERR: Unexpected closing tag "area"   area > g
<math><br><mi>x</mi></br></math>       br > mi                            ERR                                  br > mi
<svg><img><circle/></svg>              img > circle, ERR closing ":svg:svg"   siblings, no error                  img > circle, ERR
<svg><g i18n>a<area>b</area>c</g>      START_TAG__SVG_AREA + CLOSE_TAG    void placeholder                     same as Angular
  • An open element with a void name is always in a namespace, since an HTML void element is never pushed. So, as in Angular, no child closes it (should_auto_close), and its parent's end tag can't skip it silently (closed_by_parent).
  • The i18n tag placeholder uses the parser's is_void (html_to_r3.rs).
  • The open elements are walked only for a void name (is_html_void).
  • From the Codex review: an open selectorless component passes down its host tag's namespace (<MyComp:foreignObject>, ng-component without a tag), as Angular's _getPrefix reads a component's tagName.
  • Tests: element children, a void name left open, and the i18n placeholders. Each fails without its fix.

Not in this PR: other tag rules are still read by the local name inside svg/math. Main has the same behavior:

  • <svg><p><div></div></p>, <svg><li>a<li>b: closed by a child
  • <svg><li>a</svg>: no error
  • <svg><pre>\nx</pre>: first newline dropped
  • whitespace kept in <svg><pre>
  • <svg><p/>

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 31, 2026 19:55
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T06:00:59.771373Z d96e722 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

Follow-up to the namespace check for void names, from a review against
Angular 22.2:

- An open element with a void name is in a namespace (an HTML void
  element is never pushed), so Angular gives `:svg:area` the default tag
  definition: no child closes it and its parent's end tag can't skip it.
  `<svg><area><g></g></area></svg>` failed with `Unexpected closing tag
  "area"`, and `<svg><img><circle/></svg>` gave no error (Angular:
  `Unexpected closing tag ":svg:svg"`).
- An i18n tag placeholder reads the parser's `is_void`: `<area>` in svg
  gets `START_TAG__SVG_AREA` and `CLOSE_TAG__SVG_AREA`.
- The open elements are walked only for a void name (`is_html_void`).

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

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95384184ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

An open selectorless component is stored under its class name, so
`<svg><MyComp:foreignObject><input></MyComp:foreignObject></svg>` missed
the `foreignObject` boundary and read `input` as `:svg:input`. Like
Angular's `_getPrefix`, which reads a component's `tagName`, the walk now
uses the component's prefix and host tag (`ng-component` without one).

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

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: d96e72281c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn
Brooooooklyn merged commit 4b4cada into voidzero-dev:main Nov 2, 2026
9 checks passed
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.

3 participants