Skip to content

[release/stable] Resolve consecutive WithoutEndTag tag helpers as siblings - #84782

Open
github-actions[bot] wants to merge 2 commits into
release/stablefrom
backport/pr-84771-to-release/stable
Open

[release/stable] Resolve consecutive WithoutEndTag tag helpers as siblings#84782
github-actions[bot] wants to merge 2 commits into
release/stablefrom
backport/pr-84771-to-release/stable

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Backport of #84771 to release/stable

/cc @chsienki

Customer Impact

Regression

  • Yes
  • No

[If yes, specify when the regression was introduced. Provide the PR or commit if known.]

Testing

[How was the fix verified? How was the issue missed previously? What tests were added?]

Risk

[High/Medium/Low. Justify the indication by mentioning how risks were measured and addressed.]

Microsoft Reviewers: Open in CodeFlow

## Summary

Consecutive tag helpers declared with `TagStructure.WithoutEndTag` and
written without a self-closing slash (e.g.
`<meta-description><meta-keywords><head-custom>`) only bind the
**first** helper. The rest are emitted as literal, unprocessed markup.

This is a regression from the deferred tag helper lowering work
(dotnet/razor#12957), which moved tag helper resolution after IR
lowering. It was reported downstream as
[dotnet/aspnetcore#68193](https://github.com/dotnet/aspnetcore/issues/68193):
a GrandNode app rendered a blank page (`Uncaught ReferenceError: Vue is
not defined`) because the `<head>` script-registration tag helpers
silently stopped running and the Vue bundle was never emitted.

## Root cause

The HTML parser nests consecutive unclosed tags, so `<a><b><c>` parses
as `a > b > c`. When `DefaultTagHelperResolutionPhase.ResolveElement`
binds `a` as a `StartTagOnly` helper, it promotes `a`'s parser-nested
body children to be **siblings** inserted *after* `a`. But the element
walker `ResolveElements` iterates children in reverse, so it has already
moved past that position and never resolves the promoted siblings. They
remain `UnresolvedElementIntermediateNode`s and are later unwrapped to
literal HTML.

The `ConvertToPlainElement` path already re-resolves its promoted
siblings; the direct `ResolveElement` `StartTagOnly` path was missing
the equivalent step.

## Fix

After promoting the `StartTagOnly` element's children to siblings,
resolve them in place (in reverse, since resolving a promoted
`StartTagOnly` sibling can itself insert further siblings). This mirrors
the existing pattern in `ConvertToPlainElementAndResolve`.

## Testing

Two new integration tests in `TagHelpersIntegrationTest`:

- `ConsecutiveWithoutEndTagTagHelpers_AllBind` -- three consecutive
`WithoutEndTag` helpers all bind.
- `MixedNestedStartTagOnlyAndHtmlTagHelpers_AllResolveCorrectly` -- a
tangled mix of a nestable helper, consecutive `WithoutEndTag` helpers, a
normal helper, and real HTML (`<section>`, `<div>`); asserts every
helper binds in document order and real markup is preserved.

Both fail without the fix (only the first helper binds) and pass with it
(net10.0 and net472).

> Note: this regression is also present in the shipping .NET 10 GA Razor
compiler, so a servicing backport is likely warranted.

###### Microsoft Reviewers: [Open in
CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/dotnet/roslyn/pull/84771)

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e4a482bf-ffc0-4a98-a673-863b5e84c6d8
@github-actions
github-actions Bot requested a review from a team as a code owner August 6, 2026 03:49
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

The backport squash kept release/stable's using block, dropping the
Microsoft.AspNetCore.Razor.Language.Intermediate and Roslyn.Test.Utilities
imports the tests need, which broke the build with CS0246 on TagHelper IR
types and the WorkItem attribute. Add them back.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e4a482bf-ffc0-4a98-a673-863b5e84c6d8
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.

1 participant