From 3a1e17fc6daca8bf14e6c56d88b990437c0076d5 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 5 Aug 2026 17:24:23 -0700 Subject: [PATCH 1/2] Resolve consecutive WithoutEndTag tag helpers as siblings (#84771) ## Summary Consecutive tag helpers declared with `TagStructure.WithoutEndTag` and written without a self-closing slash (e.g. ``) 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 `` script-registration tag helpers silently stopped running and the Vue bundle was never emitted. ## Root cause The HTML parser nests consecutive unclosed tags, so `` 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 (`
`, `
`); 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 --- .../TagHelpersIntegrationTest.cs | 238 +++++++++++++++++- ...tedWithoutEndTagTagHelpers_Baseline.cshtml | 11 + ...tedWithoutEndTagTagHelpers_Baseline.ir.txt | 63 +++++ .../DefaultTagHelperResolutionPhase.cs | 25 ++ 4 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.cshtml create mode 100644 src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.ir.txt diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs index 0959e0433c38..77827450d8e8 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs @@ -181,11 +181,237 @@ public void AddTagHelperDirective_StoresDirectiveTagHelperContributions() Assert.NotEmpty(contribution.ContributedTagHelpers); } + [Fact] + [WorkItem("https://github.com/dotnet/aspnetcore/issues/68193")] + public void ConsecutiveWithoutEndTagTagHelpers_AllBind() + { + // The HTML parser nests consecutive unclosed tags (`` becomes + // alpha > beta > gamma). Each of these is a WithoutEndTag tag helper, so all three + // must still bind as siblings rather than only the first. + TagHelperCollection tagHelpers = + [ + CreateTagHelperDescriptor( + tagName: "alpha", + typeName: "AlphaTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + CreateTagHelperDescriptor( + tagName: "beta", + typeName: "BetaTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + CreateTagHelperDescriptor( + tagName: "gamma", + typeName: "GammaTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + ]; + + var projectEngine = CreateProjectEngine(builder => builder.SetTagHelpers(tagHelpers)); + var projectItem = AddProjectItemFromText(""" + @addTagHelper *, TestAssembly + + + + + + """, filePath: "Index.cshtml"); + + // Act + var codeDocument = projectEngine.Process(projectItem); + + // Assert + var documentNode = codeDocument.GetRequiredDocumentNode(); + var tagHelperNodes = documentNode.FindDescendantNodes(); + Assert.Collection(tagHelperNodes, + node => Assert.Equal("alpha", node.TagName), + node => Assert.Equal("beta", node.TagName), + node => Assert.Equal("gamma", node.TagName)); + + // They are siblings, not nested: each WithoutEndTag helper promoted its body out, so + // none of them contains another tag helper. + Assert.All(tagHelperNodes, node => Assert.Empty(node.FindDescendantNodes())); + } + + [Fact] + [WorkItem("https://github.com/dotnet/aspnetcore/issues/68193")] + public void MixedNestedStartTagOnlyAndHtmlTagHelpers_AllResolveCorrectly() + { + // A deliberately tangled mix: a nestable tag helper (wrapper) containing consecutive + // WithoutEndTag helpers (alpha, beta) and a normal one (bold), followed by a second + // WithoutEndTag helper whose parser-nested body wraps a real HTML element (
) and + // yet another WithoutEndTag helper. Every tag helper must bind, real HTML (
, + //
) must stay markup, and document order must be preserved. + // Note: custom (non-void) tag names are required -- the HTML parser self-terminates + // real void elements like /
, which wouldn't exercise the nesting-promotion path. + TagHelperCollection tagHelpers = + [ + CreateTagHelperDescriptor( + tagName: "wrapper", + typeName: "WrapperTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "bold", + typeName: "BoldTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "alpha", + typeName: "AlphaTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + CreateTagHelperDescriptor( + tagName: "beta", + typeName: "BetaTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + ]; + + var projectEngine = CreateProjectEngine(builder => builder.SetTagHelpers(tagHelpers)); + var projectItem = AddProjectItemFromText(""" + @addTagHelper *, TestAssembly +
+ + + + text + + +
plain html
+ +
+ """, filePath: "Index.cshtml"); + + // Act + var codeDocument = projectEngine.Process(projectItem); + + // Assert: every tag helper binds, in document order, with the WithoutEndTag helpers + // promoted to siblings rather than swallowing what follows them. + var documentNode = codeDocument.GetRequiredDocumentNode(); + var tagHelperNodes = documentNode.FindDescendantNodes(); + Assert.Collection(tagHelperNodes, + node => Assert.Equal("wrapper", node.TagName), + node => Assert.Equal("alpha", node.TagName), + node => Assert.Equal("beta", node.TagName), + node => Assert.Equal("bold", node.TagName), + node => Assert.Equal("alpha", node.TagName), + node => Assert.Equal("beta", node.TagName)); + + // Structure: wrapper genuinely contains alpha/beta/bold, while the WithoutEndTag helpers + // stay flat -- their bodies were promoted out, so none nests another tag helper. + Assert.Collection(tagHelperNodes[0].FindDescendantNodes(), + node => Assert.Equal("alpha", node.TagName), + node => Assert.Equal("beta", node.TagName), + node => Assert.Equal("bold", node.TagName)); + Assert.Empty(tagHelperNodes[1].FindDescendantNodes()); // alpha inside wrapper + Assert.Empty(tagHelperNodes[2].FindDescendantNodes()); // beta inside wrapper + Assert.Empty(tagHelperNodes[4].FindDescendantNodes()); // alpha at top level + Assert.Empty(tagHelperNodes[5].FindDescendantNodes()); // beta at top level + + // The real HTML elements must survive as literal markup, never bound as tag helpers. + var generatedCode = codeDocument.GetRequiredCSharpDocument().Text.ToString(); + Assert.Contains("
", generatedCode); + Assert.Contains("
", generatedCode); + Assert.Contains("plain html", generatedCode); + } + + [Fact] + [WorkItem("https://github.com/dotnet/aspnetcore/issues/68193")] + public void PromotedStartTagOnlySibling_BindsUsingParentTagHelperContext() + { + // `child` is a WithoutEndTag helper that only matches when its parent is `wrapper`. The + // parser nests it under the preceding WithoutEndTag helper `lead` (), so it + // is reached only after `lead` promotes it to a sibling inside wrapper's body. That + // re-resolution must carry wrapper as the parent-tag context, otherwise the + // RequireParentTag("wrapper") rule can't match and `child` silently fails to bind. + TagHelperCollection tagHelpers = + [ + CreateTagHelperDescriptor( + tagName: "wrapper", + typeName: "WrapperTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "lead", + typeName: "LeadTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + CreateTagHelperDescriptor( + tagName: "child", + typeName: "ChildTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag, + parentTag: "wrapper"), + ]; + + var projectEngine = CreateProjectEngine(builder => builder.SetTagHelpers(tagHelpers)); + var projectItem = AddProjectItemFromText(""" + @addTagHelper *, TestAssembly + + + + + """, filePath: "Index.cshtml"); + + // Act + var codeDocument = projectEngine.Process(projectItem); + + // Assert: child binds because the promoted sibling was resolved with wrapper as its + // parent-tag context. + var documentNode = codeDocument.GetRequiredDocumentNode(); + var tagHelperNodes = documentNode.FindDescendantNodes(); + Assert.Collection(tagHelperNodes, + node => Assert.Equal("wrapper", node.TagName), + node => Assert.Equal("lead", node.TagName), + node => Assert.Equal("child", node.TagName)); + } + + [Fact] + [WorkItem("https://github.com/dotnet/aspnetcore/issues/68193")] + public void PromotedWithoutEndTagTagHelpers_Baseline() + { + // Full-IR structural baseline for the promotion scenario: a nestable helper containing + // consecutive WithoutEndTag helpers plus a normal one, followed by a second WithoutEndTag + // helper whose parser-nested body wraps real HTML and another helper. The .ir.txt baseline + // captures the exact tree shape, proving the promoted helpers become siblings (not nested) + // and that the real HTML survives as markup. + TagHelperCollection tagHelpers = + [ + CreateTagHelperDescriptor( + tagName: "wrapper", + typeName: "WrapperTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "bold", + typeName: "BoldTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "alpha", + typeName: "AlphaTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + CreateTagHelperDescriptor( + tagName: "beta", + typeName: "BetaTagHelper", + assemblyName: "TestAssembly", + tagStructure: TagStructure.WithoutEndTag), + ]; + + var projectEngine = CreateProjectEngine(builder => builder.SetTagHelpers(tagHelpers)); + var projectItem = CreateProjectItemFromFile(); + + // Act + var codeDocument = projectEngine.Process(projectItem); + + // Assert + AssertDocumentNodeMatchesBaseline(codeDocument.GetRequiredDocumentNode()); + } + private static TagHelperDescriptor CreateTagHelperDescriptor( string tagName, string typeName, string assemblyName, - IEnumerable>? attributes = null) + IEnumerable>? attributes = null, + TagStructure tagStructure = TagStructure.Unspecified, + string? parentTag = null) { var builder = TagHelperDescriptorBuilder.CreateTagHelper(typeName, assemblyName); builder.SetTypeName(typeName, typeNamespace: null, typeNameIdentifier: null); @@ -198,7 +424,15 @@ private static TagHelperDescriptor CreateTagHelperDescriptor( } } - builder.TagMatchingRuleDescriptor(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + builder.TagMatchingRuleDescriptor(ruleBuilder => + { + ruleBuilder.RequireTagName(tagName).RequireTagStructure(tagStructure); + + if (parentTag != null) + { + ruleBuilder.RequireParentTag(parentTag); + } + }); var descriptor = builder.Build(); diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.cshtml b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.cshtml new file mode 100644 index 000000000000..01c3dbc9a9c4 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.cshtml @@ -0,0 +1,11 @@ +@addTagHelper *, TestAssembly +
+ + + +text + + +
plain html
+ +
diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.ir.txt b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.ir.txt new file mode 100644 index 000000000000..78b6b4e0de66 --- /dev/null +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/TagHelpersIntegrationTest/PromotedWithoutEndTagTagHelpers_Baseline.ir.txt @@ -0,0 +1,63 @@ +Document - + RazorCompiledItemAttribute - + NamespaceDeclaration - - Razor + RazorSourceChecksumAttribute - + ClassDeclaration - - public - Template - - + DefaultTagHelperRuntime - + FieldDeclaration - - private - global::WrapperTagHelper - __WrapperTagHelper + FieldDeclaration - - private - global::AlphaTagHelper - __AlphaTagHelper + FieldDeclaration - - private - global::BetaTagHelper - __BetaTagHelper + FieldDeclaration - - private - global::BoldTagHelper - __BoldTagHelper + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + HtmlContent - (31:1,0 [11] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (31:1,0 [8] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html -
+ LazyIntermediateToken - (40:1,9 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + TagHelper - (42:2,0 [57] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - wrapper - TagMode.StartTagAndEndTag + DefaultTagHelperBody - + HtmlContent - (51:2,9 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (51:2,9 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + TagHelper - (53:3,0 [36] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - alpha - TagMode.StartTagOnly + DefaultTagHelperBody - + DefaultTagHelperCreate - - AlphaTagHelper + DefaultTagHelperExecute - + HtmlContent - (60:3,7 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (60:3,7 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + TagHelper - (62:4,0 [27] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - beta - TagMode.StartTagOnly + DefaultTagHelperBody - + DefaultTagHelperCreate - - BetaTagHelper + DefaultTagHelperExecute - + HtmlContent - (68:4,6 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (68:4,6 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + TagHelper - (70:5,0 [17] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - bold - TagMode.StartTagAndEndTag + DefaultTagHelperBody - + HtmlContent - (76:5,6 [4] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (76:5,6 [4] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - text + DefaultTagHelperCreate - - BoldTagHelper + DefaultTagHelperExecute - + HtmlContent - (87:5,17 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (87:5,17 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + DefaultTagHelperCreate - - WrapperTagHelper + DefaultTagHelperExecute - + HtmlContent - (99:6,10 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (99:6,10 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + TagHelper - (101:7,0 [40] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - alpha - TagMode.StartTagOnly + DefaultTagHelperBody - + DefaultTagHelperCreate - - AlphaTagHelper + DefaultTagHelperExecute - + HtmlContent - (108:7,7 [25] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (108:7,7 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + LazyIntermediateToken - (110:8,0 [4] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html -
+ LazyIntermediateToken - (115:8,5 [10] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - plain html + LazyIntermediateToken - (125:8,15 [6] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html -
+ LazyIntermediateToken - (131:8,21 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + TagHelper - (133:9,0 [8] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - beta - TagMode.StartTagOnly + DefaultTagHelperBody - + DefaultTagHelperCreate - - BetaTagHelper + DefaultTagHelperExecute - + HtmlContent - (139:9,6 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (139:9,6 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n + HtmlContent - (141:10,0 [12] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) + LazyIntermediateToken - (141:10,0 [10] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html -
+ LazyIntermediateToken - (151:10,10 [2] PromotedWithoutEndTagTagHelpers_Baseline.cshtml) - Html - \n diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs index b1d53bc93549..80a857504776 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultTagHelperResolutionPhase.cs @@ -197,6 +197,31 @@ private void ResolveElement( { parent.Children.Insert(insertIdx++, elementNode.Children[i]); } + + // The promoted nodes were parsed as body children of this element because the + // HTML parser nests unclosed tags (e.g. `
` becomes a > b > c). Now that + // this element is bound as StartTagOnly, they are siblings that may themselves be + // tag helpers. The outer walker iterates in reverse and won't revisit these + // newly inserted positions, so resolve them here (in reverse, since resolving a + // promoted StartTagOnly sibling can insert further siblings after it). + for (var j = insertIdx - 1; j > index; j--) + { + if (j < parent.Children.Count) + { + if (parent.Children[j] is UnresolvedElementIntermediateNode promotedElement) + { + // Forward tagHelperParent: the promoted siblings live in the same + // container as this StartTagOnly element, so they share its parent-tag + // context. Dropping it would break bindings that depend on the parent + // (e.g. RequireParentTag or component child-content matching). + ResolveElement(parent, j, promotedElement, binder, prefix, usedHelpers, in context, tagHelperParent); + } + else + { + ResolveElements(parent.Children[j], binder, prefix, usedHelpers, in context); + } + } + } } } } From 5eb281a21c65681098e83e547d6001ee8a0da332 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Thu, 6 Aug 2026 17:00:13 -0700 Subject: [PATCH 2/2] Restore using directives dropped during backport 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 --- .../test/IntegrationTests/TagHelpersIntegrationTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs index 77827450d8e8..059332af935a 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/TagHelpersIntegrationTest.cs @@ -5,7 +5,9 @@ using System.Collections.Immutable; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.Language.Syntax; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests;