From 48bca5b2f896b487de71bc19ec33d46e3bc529a8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 2 Jul 2025 16:14:33 -0700 Subject: [PATCH 01/10] Introduce IntermedateNodeFactory Add `IntermediateNodeFactory` static class with factory methods for creating CSharp and Html `IntermediateTokens`. --- .../Intermediate/IntermediateNodeFactory.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs new file mode 100644 index 00000000000..942b830b408 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate; + +internal static class IntermediateNodeFactory +{ + public static IntermediateToken CSharpToken(string content, SourceSpan? source = null) + => new() { Content = content, Kind = TokenKind.CSharp, Source = source }; + + public static LazyIntermediateToken CSharpToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) + => new() { FactoryArgument = factoryArgument, ContentFactory = contentFactory, Kind = TokenKind.CSharp, Source = source }; + + public static IntermediateToken HtmlToken(string content, SourceSpan? source = null) + => new() { Content = content, Kind = TokenKind.Html, Source = source }; + + public static LazyIntermediateToken HtmlToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) + => new() { FactoryArgument = factoryArgument, ContentFactory = contentFactory, Kind = TokenKind.Html, Source = source }; +} From aba60469f27c7ffc8b34f82055a0fdc387d08e95 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 2 Jul 2025 16:15:29 -0700 Subject: [PATCH 02/10] Replace calls to IntermedateToken constructor with node factory methods --- .../test/InstrumentationPassTest.cs | 43 +--- .../DefaultDocumentWriterTest.cs | 106 +++------ .../DesignTimeNodeWriterTest.cs | 75 ++---- .../LiteralRuntimeNodeWriterTest.cs | 30 +-- .../CodeGeneration/RuntimeNodeWriterTest.cs | 77 +----- .../DefaultTagHelperTargetExtensionTest.cs | 150 ++++++------ .../Components/ComponentBindLoweringPass.cs | 221 ++++-------------- .../ComponentDesignTimeNodeWriter.cs | 36 +-- .../ComponentEventHandlerLoweringPass.cs | 17 +- .../ComponentLayoutDirectivePass.cs | 6 +- .../ComponentRenderModeDirectivePass.cs | 36 +-- .../Components/ComponentRuntimeNodeWriter.cs | 30 +-- .../Extensions/AttributeDirectivePass.cs | 7 +- .../Extensions/DesignTimeDirectivePass.cs | 31 +-- .../Extensions/EliminateMethodBodyPass.cs | 28 +-- .../Extensions/ImplementsDirectivePass.cs | 2 +- .../Language/Extensions/ViewCssScopePass.cs | 7 +- .../Intermediate/BaseTypeWithModel.cs | 12 +- .../Intermediate/IntermediateToken.cs | 2 - .../AssemblyAttributeInjectionPass.cs | 14 +- .../src/Mvc.Version2_X/InstrumentationPass.cs | 24 +- .../PagesPropertyInjectionPass.cs | 13 +- .../src/Mvc/ModelDirective.cs | 6 +- .../src/Mvc/ModelExpressionPass.cs | 27 +-- .../src/Mvc/PagesPropertyInjectionPass.cs | 14 +- .../test/GenericTypeNameRewriterTest.cs | 12 +- 26 files changed, 285 insertions(+), 741 deletions(-) diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/InstrumentationPassTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/InstrumentationPassTest.cs index f25aca05b22..098b5972534 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/InstrumentationPassTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/InstrumentationPassTest.cs @@ -23,11 +23,7 @@ public void InstrumentationPass_NoOps_ForDesignTime() builder.Push(new HtmlContentIntermediateNode()); - builder.Add(new IntermediateToken() - { - Content = "Hi", - Kind = TokenKind.Html - }); + builder.Add(IntermediateNodeFactory.HtmlToken("Hi")); builder.Pop(); @@ -54,12 +50,7 @@ public void InstrumentationPass_InstrumentsHtml() Source = CreateSource(1), }); - builder.Add(new IntermediateToken() - { - Content = "Hi", - Kind = TokenKind.Html, - Source = CreateSource(1) - }); + builder.Add(IntermediateNodeFactory.HtmlToken("Hi", CreateSource(1))); builder.Pop(); @@ -85,11 +76,7 @@ public void InstrumentationPass_SkipsHtml_WithoutLocation() builder.Push(new HtmlContentIntermediateNode()); - builder.Add(new IntermediateToken() - { - Content = "Hi", - Kind = TokenKind.Html - }); + builder.Add(IntermediateNodeFactory.HtmlToken("Hi")); builder.Pop(); @@ -116,11 +103,7 @@ public void InstrumentationPass_InstrumentsCSharpExpression() Source = CreateSource(2), }); - builder.Add(new IntermediateToken() - { - Content = "Hi", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("Hi")); // Act ProjectEngine.ExecutePass(codeDocument, documentNode); @@ -144,11 +127,7 @@ public void InstrumentationPass_SkipsCSharpExpression_WithoutLocation() builder.Push(new CSharpExpressionIntermediateNode()); - builder.Add(new IntermediateToken() - { - Content = "Hi", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("Hi")); // Act ProjectEngine.ExecutePass(codeDocument, documentNode); @@ -177,11 +156,7 @@ public void InstrumentationPass_SkipsCSharpExpression_InsideTagHelperAttribute() Source = CreateSource(5) }); - builder.Add(new IntermediateToken() - { - Content = "Hi", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("Hi")); // Act ProjectEngine.ExecutePass(codeDocument, documentNode); @@ -222,11 +197,7 @@ public void InstrumentationPass_SkipsCSharpExpression_InsideTagHelperProperty() Source = CreateSource(5) }); - builder.Add(new IntermediateToken() - { - Content = "Hi", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("Hi")); // Act ProjectEngine.ExecutePass(codeDocument, documentNode); diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DefaultDocumentWriterTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DefaultDocumentWriterTest.cs index 49a20dd1bbf..6fcebda98f6 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DefaultDocumentWriterTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DefaultDocumentWriterTest.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - -using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -175,18 +172,14 @@ public void WriteDocument_WritesClass() var builder = IntermediateNodeBuilder.Create(document); builder.Add(new ClassDeclarationIntermediateNode() { - Modifiers = - { - "internal" - }, + Modifiers = { "internal" }, BaseType = new BaseTypeWithModel("TestBase"), - Interfaces = [IntermediateToken.CreateCSharpToken("IFoo"), IntermediateToken.CreateCSharpToken("IBar")], - TypeParameters = new List - { - new TypeParameter() { ParameterName = "TKey", }, - new TypeParameter() { ParameterName = "TValue", }, - }, - ClassName = "TestClass", + Interfaces = [IntermediateNodeFactory.CSharpToken("IFoo"), IntermediateNodeFactory. CSharpToken("IBar")], + TypeParameters = [ + new TypeParameter() { ParameterName = "TKey" }, + new TypeParameter() { ParameterName = "TValue" }, + ], + ClassName = "TestClass" }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); @@ -222,19 +215,15 @@ public void WriteDocument_WithNullableContext_WritesClass() var builder = IntermediateNodeBuilder.Create(document); builder.Add(new ClassDeclarationIntermediateNode() { - Modifiers = - { - "internal" - }, + Modifiers = { "internal" }, BaseType = new BaseTypeWithModel("TestBase"), - Interfaces = [IntermediateToken.CreateCSharpToken("IFoo"), IntermediateToken.CreateCSharpToken("IBar")], - TypeParameters = new List - { - new TypeParameter() { ParameterName = "TKey", }, - new TypeParameter() { ParameterName = "TValue", }, - }, + Interfaces = [IntermediateNodeFactory.CSharpToken("IFoo"), IntermediateNodeFactory.CSharpToken("IBar")], + TypeParameters = [ + new TypeParameter() { ParameterName = "TKey" }, + new TypeParameter() { ParameterName = "TValue" }, + ], ClassName = "TestClass", - NullableContext = true, + NullableContext = true }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); @@ -272,18 +261,14 @@ public void WriteDocument_WritesClass_ConstrainedGenericTypeParameters() var builder = IntermediateNodeBuilder.Create(document); builder.Add(new ClassDeclarationIntermediateNode() { - Modifiers = - { - "internal" - }, + Modifiers = { "internal" }, BaseType = new BaseTypeWithModel("TestBase"), - Interfaces = [IntermediateToken.CreateCSharpToken("IFoo"), IntermediateToken.CreateCSharpToken("IBar")], - TypeParameters = new List - { - new TypeParameter() { ParameterName = "TKey", Constraints = "where TKey : class" }, - new TypeParameter() { ParameterName = "TValue", Constraints = "where TValue : class" }, - }, - ClassName = "TestClass", + Interfaces = [IntermediateNodeFactory.CSharpToken("IFoo"), IntermediateNodeFactory.CSharpToken("IBar")], + TypeParameters = [ + new TypeParameter() { ParameterName = "TKey", Constraints = "where TKey : class" }, + new TypeParameter() { ParameterName = "TValue", Constraints = "where TValue : class" }, + ], + ClassName = "TestClass" }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); @@ -321,32 +306,23 @@ public void WriteDocument_WritesMethod() var builder = IntermediateNodeBuilder.Create(document); builder.Add(new MethodDeclarationIntermediateNode() { - Modifiers = - { - "internal", - "virtual", - "async", - }, + Modifiers = { "internal", "virtual", "async", }, MethodName = "TestMethod", Parameters = + { + new MethodParameter() { - new MethodParameter() - { - Modifiers = - { - "readonly", - "ref", - }, - ParameterName = "a", - TypeName = "int", - }, - new MethodParameter() - { - ParameterName = "b", - TypeName = "string", - } + Modifiers = { "readonly", "ref" }, + ParameterName = "a", + TypeName = "int" }, - ReturnType = "string", + new MethodParameter() + { + ParameterName = "b", + TypeName = "string" + } + }, + ReturnType = "string" }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); @@ -384,11 +360,7 @@ public void WriteDocument_WritesField() var builder = IntermediateNodeBuilder.Create(document); builder.Add(new FieldDeclarationIntermediateNode() { - Modifiers = - { - "internal", - "readonly", - }, + Modifiers = { "internal", "readonly", }, FieldName = "_foo", FieldType = "string", }); @@ -424,13 +396,9 @@ public void WriteDocument_WritesProperty() var builder = IntermediateNodeBuilder.Create(document); builder.Add(new PropertyDeclarationIntermediateNode() { - Modifiers = - { - "internal", - "virtual", - }, + Modifiers = { "internal", "virtual", }, PropertyName = "Foo", - PropertyType = IntermediateToken.CreateCSharpToken("string"), + PropertyType = IntermediateNodeFactory.CSharpToken("string"), PropertyExpression = "default" }); diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs index 7cd297095c0..f7e23f0b44e 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs @@ -125,11 +125,7 @@ public void WriteCSharpExpression_SkipsLinePragma_WithoutSource() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i++")); // Act writer.WriteCSharpExpression(context, node); @@ -157,11 +153,7 @@ public void WriteCSharpExpression_WritesLinePragma_WithSource() var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i++")); // Act writer.WriteCSharpExpression(context, node); @@ -192,19 +184,11 @@ public void WriteCSharpExpression_WithExtensionNode_WritesPadding() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i")); builder.Add(new MyExtensionIntermediateNode()); - builder.Add(new IntermediateToken() - { - Content = "++", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("++")); // Act writer.WriteCSharpExpression(context, node); @@ -232,19 +216,11 @@ public void WriteCSharpExpression_WithSource_WritesPadding() }; var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i")); builder.Add(new MyExtensionIntermediateNode()); - builder.Add(new IntermediateToken() - { - Content = "++", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("++")); // Act writer.WriteCSharpExpression(context, node); @@ -279,11 +255,7 @@ public void WriteCSharpCode_WhitespaceContentWithSource_WritesContent() }; IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = " " - }); + .Add(IntermediateNodeFactory.CSharpToken(" ")); // Act writer.WriteCSharpCode(context, node); @@ -313,11 +285,7 @@ public void WriteCSharpCode_SkipsLinePragma_WithoutSource() var node = new CSharpCodeIntermediateNode(); IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "if (true) { }" - }); + .Add(IntermediateNodeFactory.CSharpToken("if (true) { }")); // Act writer.WriteCSharpCode(context, node); @@ -344,11 +312,7 @@ public void WriteCSharpCode_WritesLinePragma_WithSource() }; IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "if (true) { }", - }); + .Add(IntermediateNodeFactory.CSharpToken("if (true) { }")); // Act writer.WriteCSharpCode(context, node); @@ -382,11 +346,7 @@ public void WriteCSharpCode_WritesPadding_WithSource() }; IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = " if (true) { }", - }); + .Add(IntermediateNodeFactory.CSharpToken(" if (true) { }")); // Act writer.WriteCSharpCode(context, node); @@ -535,11 +495,7 @@ public void LinePragma_Is_Adjusted_On_Windows(string fileName, string expectedFi }; var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i++")); writer.WriteCSharpExpression(context, node); @@ -581,13 +537,8 @@ public void LinePragma_Enhanced_Is_Adjusted_On_Windows(string fileName, string e var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++", - Kind = TokenKind.CSharp, - // Create a fake source span, so we can check it correctly maps in the #line below - Source = new SourceSpan(fileName, 0, 2, 3, 6, 1, 2) - }); + // Create a fake source span, so we can check it correctly maps in the #line below + builder.Add(IntermediateNodeFactory.CSharpToken("i++", new SourceSpan(fileName, 0, 2, 3, 6, 1, 2))); writer.WriteCSharpExpression(context, node); diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/LiteralRuntimeNodeWriterTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/LiteralRuntimeNodeWriterTest.cs index 6ff7def84de..cbe91b970e1 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/LiteralRuntimeNodeWriterTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/LiteralRuntimeNodeWriterTest.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; @@ -19,12 +17,7 @@ public void WriteCSharpExpression_UsesWriteLiteral_WritesLinePragma_WithSource() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++", - Kind = TokenKind.CSharp, - Source = new SourceSpan("test.cshtml", 0, 0, 0, 3, 0, 3), - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i++", new SourceSpan("test.cshtml", 0, 0, 0, 3, 0, 3))); // Act writer.WriteCSharpExpression(context, node); @@ -55,24 +48,9 @@ public void WriteCSharpExpression_WithMultipleChildren() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++;", - Kind = TokenKind.CSharp, - Source = new SourceSpan("test.cshtml", 0, 0, 0, 4, 0, 4), - }); - builder.Add(new IntermediateToken() - { - Content = "j++;", - Kind = TokenKind.CSharp, - Source = new SourceSpan("test.cshtml", 5, 0, 5, 4, 0, 9), - }); - builder.Add(new IntermediateToken() - { - Content = "k++;", - Kind = TokenKind.CSharp, - Source = new SourceSpan("test.cshtml", 10, 0, 10, 4, 0, 14), - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i++;", new SourceSpan("test.cshtml", 0, 0, 0, 4, 0, 4))); + builder.Add(IntermediateNodeFactory.CSharpToken("j++;", new SourceSpan("test.cshtml", 5, 0, 5, 4, 0, 9))); + builder.Add(IntermediateNodeFactory.CSharpToken("k++;", new SourceSpan("test.cshtml", 10, 0, 10, 4, 0, 14))); // Act writer.WriteCSharpExpression(context, node); diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/RuntimeNodeWriterTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/RuntimeNodeWriterTest.cs index 509955c47e7..c09ef8d3c8c 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/RuntimeNodeWriterTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/RuntimeNodeWriterTest.cs @@ -120,11 +120,7 @@ public void WriteCSharpExpression_SkipsLinePragma_WithoutSource() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i++")); // Act writer.WriteCSharpExpression(context, node); @@ -152,12 +148,7 @@ public void WriteCSharpExpression_WritesLinePragma_WithSource() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i++", - Kind = TokenKind.CSharp, - Source = new SourceSpan("test.cshtml", 0, 0, 0, 3, 0, 3) - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i++", new SourceSpan("test.cshtml", 0, 0, 0, 3, 0, 3))); // Act writer.WriteCSharpExpression(context, node); @@ -192,19 +183,11 @@ public void WriteCSharpExpression_WithExtensionNode_WritesPadding() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i")); builder.Add(new MyExtensionIntermediateNode()); - builder.Add(new IntermediateToken() - { - Content = "++", - Kind = TokenKind.CSharp - }); + builder.Add(IntermediateNodeFactory.CSharpToken("++")); // Act writer.WriteCSharpExpression(context, node); @@ -233,21 +216,11 @@ public void WriteCSharpExpression_WithSource_WritesPadding() var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); - builder.Add(new IntermediateToken() - { - Content = "i", - Kind = TokenKind.CSharp, - Source = new SourceSpan("test.cshtml", 0, 0, 0, 1, 0, 1) - }); + builder.Add(IntermediateNodeFactory.CSharpToken("i", new SourceSpan("test.cshtml", 0, 0, 0, 1, 0, 1))); builder.Add(new MyExtensionIntermediateNode()); - builder.Add(new IntermediateToken() - { - Content = "++", - Kind = TokenKind.CSharp, - Source = new SourceSpan("test.cshtml", 2, 0, 2, 2, 0, 4) - }); + builder.Add(IntermediateNodeFactory.CSharpToken("++", new SourceSpan("test.cshtml", 2, 0, 2, 2, 0, 4))); // Act writer.WriteCSharpExpression(context, node); @@ -286,11 +259,7 @@ public void WriteCSharpCode_WhitespaceContent_DoesNothing() var node = new CSharpCodeIntermediateNode(); IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = " \t" - }); + .Add(IntermediateNodeFactory.CSharpToken(" \t")); // Act writer.WriteCSharpCode(context, node); @@ -309,11 +278,7 @@ public void WriteCSharpCode_SkipsLinePragma_WithoutSource() var node = new CSharpCodeIntermediateNode(); IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "if (true) { }" - }); + .Add(IntermediateNodeFactory.CSharpToken("if (true) { }")); // Act writer.WriteCSharpCode(context, node); @@ -336,12 +301,7 @@ public void WriteCSharpCode_WritesLinePragma_WithSource() var node = new CSharpCodeIntermediateNode(); IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "if (true) { }", - Source = new SourceSpan("test.cshtml", 0, 0, 0, 13) - }); + .Add(IntermediateNodeFactory.CSharpToken("if (true) { }", new SourceSpan("test.cshtml", 0, 0, 0, 13))); // Act writer.WriteCSharpCode(context, node); @@ -372,12 +332,7 @@ public void WriteCSharpCode_WritesPadding_WithSource() var node = new CSharpCodeIntermediateNode(); IntermediateNodeBuilder.Create(node) - .Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = " if (true) { }", - Source = new SourceSpan("test.cshtml", 0, 0, 0, 17) - }); + .Add(IntermediateNodeFactory.CSharpToken(" if (true) { }", new SourceSpan("test.cshtml", 0, 0, 0, 17))); // Act writer.WriteCSharpCode(context, node); @@ -488,11 +443,7 @@ public void WriteHtmlContent_RendersContentCorrectly() using var context = TestCodeRenderingContext.CreateRuntime(); var node = new HtmlContentIntermediateNode(); - node.Children.Add(new IntermediateToken() - { - Content = "SomeContent", - Kind = TokenKind.Html, - }); + node.Children.Add(IntermediateNodeFactory.HtmlToken("SomeContent")); // Act writer.WriteHtmlContent(context, node); @@ -514,11 +465,7 @@ public void WriteHtmlContent_LargeStringLiteral_UsesMultipleWrites() using var context = TestCodeRenderingContext.CreateRuntime(); var node = new HtmlContentIntermediateNode(); - node.Children.Add(new IntermediateToken() - { - Content = new string('*', 2000), - Kind = TokenKind.Html - }); + node.Children.Add(IntermediateNodeFactory.HtmlToken(new string('*', 2000))); // Act writer.WriteHtmlContent(context, node); diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Extensions/DefaultTagHelperTargetExtensionTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Extensions/DefaultTagHelperTargetExtensionTest.cs index 1e5742a7482..d3d92cfd74b 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Extensions/DefaultTagHelperTargetExtensionTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Extensions/DefaultTagHelperTargetExtensionTest.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -245,16 +243,16 @@ public void WriteTagHelperHtmlAttribute_DesignTime_WritesNothing() AttributeName = "name", AttributeStructure = AttributeStructure.DoubleQuotes, Children = + { + new HtmlAttributeValueIntermediateNode() + { + Children = { IntermediateNodeFactory.HtmlToken("Blah-") } + }, + new CSharpCodeAttributeValueIntermediateNode() { - new HtmlAttributeValueIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.Html, Content = "Blah-" } } - }, - new CSharpCodeAttributeValueIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "\"Foo\"", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("\"Foo\"") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -285,12 +283,12 @@ public void WriteTagHelperHtmlAttribute_Runtime_SimpleAttribute_RendersCorrectly AttributeName = "name", AttributeStructure = AttributeStructure.DoubleQuotes, Children = + { + new HtmlAttributeIntermediateNode() { - new HtmlAttributeIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.Html, Content = "\"value\"", } }, - } + Children = { IntermediateNodeFactory.HtmlToken("\"value\"") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -323,16 +321,16 @@ public void WriteTagHelperHtmlAttribute_Runtime_DynamicAttribute_RendersCorrectl AttributeName = "name", AttributeStructure = AttributeStructure.DoubleQuotes, Children = + { + new HtmlAttributeValueIntermediateNode() + { + Children = { IntermediateNodeFactory.HtmlToken("Blah-") } + }, + new CSharpCodeAttributeValueIntermediateNode() { - new HtmlAttributeValueIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.Html, Content = "Blah-" } } - }, - new CSharpCodeAttributeValueIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "\"Foo\"", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("\"Foo\"") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -438,12 +436,12 @@ public void WriteTagHelperProperty_DesignTime_StringProperty_HtmlContent_Renders PropertyName = "StringProp", TagHelper = StringPropertyTagHelper, Children = + { + new HtmlContentIntermediateNode() { - new HtmlContentIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.Html, Content = "value", } }, - } + Children = { IntermediateNodeFactory.HtmlToken("value") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -479,12 +477,12 @@ public void WriteTagHelperProperty_DesignTime_StringProperty_NonHtmlContent_Rend PropertyName = "StringProp", TagHelper = StringPropertyTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "\"3+5\"", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("\"3+5\"") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -521,12 +519,12 @@ public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly TagHelper = IntPropertyTagHelper, Source = Span, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -612,12 +610,12 @@ public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly PropertyName = "IntProp", TagHelper = IntPropertyTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -653,12 +651,12 @@ public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly( TagHelper = IntIndexerTagHelper, Source = Span, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -700,12 +698,12 @@ public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly_ PropertyName = "IntIndexer", TagHelper = IntIndexerTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -740,12 +738,12 @@ public void WriteTagHelperProperty_Runtime_StringProperty_HtmlContent_RendersCor PropertyName = "StringProp", TagHelper = StringPropertyTagHelper, Children = + { + new HtmlContentIntermediateNode() { - new HtmlContentIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.Html, Content = "\"value\"", } }, - } + Children = { IntermediateNodeFactory.HtmlToken("\"value\"") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -786,12 +784,12 @@ public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly() PropertyName = "IntProp", TagHelper = IntPropertyTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", Source = Span } }, - } - }, + Children = { IntermediateNodeFactory.CSharpToken("32", Span) } + } + }, }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -879,12 +877,12 @@ public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly_Wi PropertyName = "IntProp", TagHelper = IntPropertyTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -920,12 +918,12 @@ public void WriteTagHelperProperty_Runtime_NonStringIndexer_RendersCorrectly() PropertyName = "IntIndexer", TagHelper = IntIndexerTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", Source = Span } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32", Span) } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -973,12 +971,12 @@ public void WriteTagHelperProperty_Runtime_NonStringIndexer_MultipleValues() PropertyName = "IntIndexer", TagHelper = IntIndexerTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "17", Source = Span } }, - } + Children = { IntermediateNodeFactory.CSharpToken("17", Span) } } + } }; var node2 = new DefaultTagHelperPropertyIntermediateNode() { @@ -990,12 +988,12 @@ public void WriteTagHelperProperty_Runtime_NonStringIndexer_MultipleValues() PropertyName = "IntIndexer", TagHelper = IntIndexerTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", Source = Span } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32", Span) } } + } }; tagHelperNode.Children.Add(node1); tagHelperNode.Children.Add(node2); @@ -1040,12 +1038,12 @@ public void WriteTagHelperProperty_Runtime_NonStringIndexer_RendersCorrectly_Wit PropertyName = "IntIndexer", TagHelper = IntIndexerTagHelper, Children = + { + new CSharpExpressionIntermediateNode() { - new CSharpExpressionIntermediateNode() - { - Children = { new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, - } + Children = { IntermediateNodeFactory.CSharpToken("32") } } + } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); @@ -1155,7 +1153,7 @@ private static TagHelperDescriptor CreateTagHelperDescriptor( string tagName, string typeName, string assemblyName, - IEnumerable> attributes = null) + IEnumerable>? attributes = null) { var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName); builder.Metadata(TypeName(typeName)); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs index 59915f8d654..5ba6208a5f8 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs @@ -400,11 +400,7 @@ private IntermediateNode[] RewriteUsage(IntermediateNode parent, BindEntry bindE else if (bindEntry.GetEffectiveNodeTagHelperDescriptor()?.GetFormat() != null) { // We may have a default format if one is associated with the field type. - format = new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "\"" + bindEntry.GetEffectiveNodeTagHelperDescriptor().GetFormat() + "\"", - }; + format = IntermediateNodeFactory.CSharpToken($"\"{bindEntry.GetEffectiveNodeTagHelperDescriptor().GetFormat()}\""); } // Look for a culture. If we find one then we need to pass the culture into the @@ -417,11 +413,7 @@ private IntermediateNode[] RewriteUsage(IntermediateNode parent, BindEntry bindE else if (bindEntry.GetEffectiveNodeTagHelperDescriptor()?.IsInvariantCultureBindTagHelper() == true) { // We may have a default invariant culture if one is associated with the field type. - culture = new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = $"global::{typeof(CultureInfo).FullName}.{nameof(CultureInfo.InvariantCulture)}", - }; + culture = IntermediateNodeFactory.CSharpToken($"global::{typeof(CultureInfo).FullName}.{nameof(CultureInfo.InvariantCulture)}"); } // Look for an after event. If we find one then we need to pass the event into the @@ -578,11 +570,7 @@ private IntermediateNode[] RewriteUsage(IntermediateNode parent, BindEntry bindE expressionNode.Children.Clear(); expressionNode.Children.Add(new CSharpExpressionIntermediateNode()); - expressionNode.Children[0].Children.Add(new IntermediateToken() - { - Content = $"() => {original.Content}", - Kind = TokenKind.CSharp - }); + expressionNode.Children[0].Children.Add(IntermediateNodeFactory.CSharpToken($"() => {original.Content}")); builder.Add(expressionNode); } @@ -801,11 +789,7 @@ private void RewriteNodesForComponentDelegateBind( if (setter == null && after == null) { - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"__value => {original.Content} = __value", - Kind = TokenKind.CSharp, - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"__value => {original.Content} = __value")); } else if (setter != null && after == null) { @@ -824,26 +808,15 @@ private void RewriteNodesForComponentDelegateBind( asyncKeyword = "async "; } - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"{asyncKeyword} __value => {{ {original.Content} = __value; {awaitKeyword}{invokeDelegateMethod}(", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($"{asyncKeyword} __value => {{ {original.Content} = __value; {awaitKeyword}{invokeDelegateMethod}(")); changeExpressionTokens.Add(after); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = "); }", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken("); }")); } else { // Treat this as the original case, since we don't support bind:set and bind:after simultaneously, we will produce an error. - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"__value => {original.Content} = __value", - Kind = TokenKind.CSharp, - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"__value => {original.Content} = __value")); } } @@ -860,21 +833,12 @@ private void RewriteNodesForComponentEventCallbackBind( valueExpressionTokens.Add(original); // This is largely the same as the one for elements as we can invoke CreateInferredCallback all the way to victory - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"{ComponentsApi.RuntimeHelpers.CreateInferredEventCallback}(this, ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"{ComponentsApi.RuntimeHelpers.CreateInferredEventCallback}(this, ")); if (setter == null && after == null) { // no bind:set nor bind:after, assign to the bound expression - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"__value => {original.Content} = __value", - Kind = TokenKind.CSharp - }); - + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"__value => {original.Content} = __value")); } else if (setter != null && after == null) { @@ -884,45 +848,24 @@ private void RewriteNodesForComponentEventCallbackBind( else if (setter == null && after != null) { // bind:after only - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"{ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: __value => {{ {original.Content} = __value; return {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($"{ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: __value => {{ {original.Content} = __value; return {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ")); changeExpressionTokens.Add(after); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"); }}, value: {original.Content})", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"); }}, value: {original.Content})")); } else { // bind:set and bind:after create the code even though we disallow this combination through a diagnostic - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"{ComponentsApi.RuntimeHelpers.CreateInferredEventCallback}(this, callback: async __value => {{ await {ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($"{ComponentsApi.RuntimeHelpers.CreateInferredEventCallback}(this, callback: async __value => {{ await {ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: ")); changeExpressionTokens.Add(setter); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $", value: {original.Content}); await {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($", value: {original.Content}); await {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ")); changeExpressionTokens.Add(after); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"); }}, value: {original.Content})", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"); }}, value: {original.Content})")); } - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $", {original.Content})", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($", {original.Content})")); } private void RewriteNodesForElementEventCallbackBind( @@ -940,38 +883,22 @@ private void RewriteNodesForElementEventCallbackBind( // Now rewrite the content of the value node to look like: // // BindConverter.FormatValue(, format: , culture: ) - valueExpressionTokens.Add(new IntermediateToken() - { - Content = $"global::{ComponentsApi.BindConverter.FormatValue}(", - Kind = TokenKind.CSharp - }); + valueExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"global::{ComponentsApi.BindConverter.FormatValue}(")); valueExpressionTokens.Add(original); if (!string.IsNullOrEmpty(format?.Content)) { - valueExpressionTokens.Add(new IntermediateToken() - { - Content = ", format: ", - Kind = TokenKind.CSharp, - }); + valueExpressionTokens.Add(IntermediateNodeFactory.CSharpToken(", format: ")); valueExpressionTokens.Add(format); } if (!string.IsNullOrEmpty(culture?.Content)) { - valueExpressionTokens.Add(new IntermediateToken() - { - Content = ", culture: ", - Kind = TokenKind.CSharp, - }); + valueExpressionTokens.Add(IntermediateNodeFactory.CSharpToken(", culture: ")); valueExpressionTokens.Add(culture); } - valueExpressionTokens.Add(new IntermediateToken() - { - Content = ")", - Kind = TokenKind.CSharp, - }); + valueExpressionTokens.Add(IntermediateNodeFactory.CSharpToken(")")); // Now rewrite the content of the change-handler node. There are two cases we care about // here. If it's a component attribute, then don't use the 'CreateBinder' wrapper. We expect @@ -990,108 +917,56 @@ private void RewriteNodesForElementEventCallbackBind( // EventCallbackFactory.CreateBinder(this, __value => = __value, , format: , culture: ) // // Note that the linemappings here are applied to the value attribute, not the change attribute. - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"global::{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($"global::{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, ")); if (setter == null && after == null) { // no bind:set nor bind:after, , assign to the bound expression - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"__value => {original.Content} = __value", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"__value => {original.Content} = __value")); } else if (setter != null && after == null) { // bind:set only - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"{ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"{ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: ")); changeExpressionTokens.Add(setter); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $", value: {original.Content})", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($", value: {original.Content})")); } else if (setter == null && after != null) { // bind:after only - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"{ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: __value => {{ {original.Content} = __value; return {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($"{ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: __value => {{ {original.Content} = __value; return {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ")); changeExpressionTokens.Add(after); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"); }}, value: {original.Content})", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"); }}, value: {original.Content})")); } else { // bind:set and bind:after create the code even though we disallow this combination through a diagnostic - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"{ComponentsApi.RuntimeHelpers.CreateInferredEventCallback}(this, callback: async __value => {{ await {ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($"{ComponentsApi.RuntimeHelpers.CreateInferredEventCallback}(this, callback: async __value => {{ await {ComponentsApi.RuntimeHelpers.CreateInferredBindSetter}(callback: ")); changeExpressionTokens.Add(setter); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $", value: {original.Content})(); await {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add( + IntermediateNodeFactory.CSharpToken($", value: {original.Content})(); await {ComponentsApi.RuntimeHelpers.InvokeAsynchronousDelegate}(callback: ")); changeExpressionTokens.Add(after); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $"); }}, value: {original.Content})", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($"); }}, value: {original.Content})")); } - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $", ", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken(", ")); - changeExpressionTokens.Add(new IntermediateToken() - { - Content = original.Content, - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken(original.Content)); if (format != null) { - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $", format: {format.Content}", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($", format: {format.Content}")); } if (culture != null) { - changeExpressionTokens.Add(new IntermediateToken() - { - Content = $", culture: {culture.Content}", - Kind = TokenKind.CSharp - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken($", culture: {culture.Content}")); } - changeExpressionTokens.Add(new IntermediateToken() - { - Content = ")", - Kind = TokenKind.CSharp, - }); + changeExpressionTokens.Add(IntermediateNodeFactory.CSharpToken(")")); } private static IntermediateToken GetAttributeContent(IntermediateNode node) @@ -1102,7 +977,7 @@ private static IntermediateToken GetAttributeContent(IntermediateNode node) { // See comments in TemplateDiagnosticPass node.AddDiagnostic(ComponentDiagnosticFactory.Create_TemplateInvalidLocation(template.Source)); - return new IntermediateToken() { Kind = TokenKind.CSharp, Content = string.Empty }; + return IntermediateNodeFactory.CSharpToken(string.Empty); } if (node.Children[0] is HtmlContentIntermediateNode htmlContentNode) @@ -1110,13 +985,13 @@ private static IntermediateToken GetAttributeContent(IntermediateNode node) // This case can be hit for a 'string' attribute. We want to turn it into // an expression. var content = "\"" + string.Join(string.Empty, htmlContentNode.Children.OfType().Select(t => t.Content)) + "\""; - return new IntermediateToken() { Kind = TokenKind.CSharp, Content = content }; + return IntermediateNodeFactory.CSharpToken(content); } - else if (node.Children[0] is CSharpExpressionIntermediateNode cSharpNode) + else if (node.Children[0] is CSharpExpressionIntermediateNode csharpNode) { // This case can be hit when the attribute has an explicit @ inside, which // 'escapes' any special sugar we provide for codegen. - return GetToken(cSharpNode); + return GetToken(csharpNode); } else { @@ -1132,11 +1007,7 @@ IntermediateToken GetToken(IntermediateNode parent) } // In error cases we won't have a single token, but we still want to generate the code. - return new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = string.Join(string.Empty, parent.Children.OfType().Select(t => t.Content)), - }; + return IntermediateNodeFactory.CSharpToken(string.Join(string.Empty, parent.Children.OfType().Select(t => t.Content))); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs index 742fb1eab1a..36cd2423d9e 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs @@ -1171,14 +1171,10 @@ protected override void WriteReferenceCaptureInnards(CodeRenderingContext contex { Source = node.Source, Children = - { - node.IdentifierToken, - new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = $" = default({captureTypeName}){nullSuppression};" - } - } + { + node.IdentifierToken, + IntermediateNodeFactory.CSharpToken($" = default({captureTypeName}){nullSuppression};") + } }); } else @@ -1195,14 +1191,10 @@ protected override void WriteReferenceCaptureInnards(CodeRenderingContext contex { Source = node.Source, Children = - { - node.IdentifierToken, - new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = $" = {refCaptureParamName};" - } - } + { + node.IdentifierToken, + IntermediateNodeFactory.CSharpToken($" = {refCaptureParamName};") + } }); } } @@ -1216,21 +1208,13 @@ public override void WriteRenderMode(CodeRenderingContext context, RenderModeInt { Children = { - new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = $"{DesignTimeVariable} = (global::{ComponentsApi.IComponentRenderMode.FullTypeName})(" - }, + IntermediateNodeFactory.CSharpToken($"{DesignTimeVariable} = (global::{ComponentsApi.IComponentRenderMode.FullTypeName})("), new CSharpCodeIntermediateNode { Source = node.Source, Children = { node.Children[0] } }, - new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = ");" - } + IntermediateNodeFactory.CSharpToken(");") } }); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs index 4b61f6b21bd..28dbff3e4f4 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs @@ -175,20 +175,11 @@ private static IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperD using var tokens = new PooledArrayBuilder(capacity: original.Length + 2); tokens.Add( - new IntermediateToken() - { - Content = $"global::{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateMethod}<{TypeNameHelper.GetGloballyQualifiedNameIfNeeded(eventArgsType)}>(this, ", - Kind = TokenKind.CSharp - }); + IntermediateNodeFactory.CSharpToken($"global::{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateMethod}<{TypeNameHelper.GetGloballyQualifiedNameIfNeeded(eventArgsType)}>(this, ")); tokens.AddRange(original); - tokens.Add( - new IntermediateToken() - { - Content = $")", - Kind = TokenKind.CSharp - }); + tokens.Add(IntermediateNodeFactory.CSharpToken(")")); var attributeName = node.AttributeName; @@ -245,7 +236,7 @@ private static ImmutableArray GetAttributeContent(Intermediat { // See comments in TemplateDiagnosticPass node.AddDiagnostic(ComponentDiagnosticFactory.Create_TemplateInvalidLocation(template.Source)); - return [new IntermediateToken() { Kind = TokenKind.CSharp, Content = string.Empty }]; + return [IntermediateNodeFactory.CSharpToken(string.Empty)]; } if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlContentNode) @@ -255,7 +246,7 @@ private static ImmutableArray GetAttributeContent(Intermediat var tokens = htmlContentNode.FindDescendantNodes(); var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content.Replace("\"", "\\\""))) + "\""; - return [new IntermediateToken() { Content = content, Kind = TokenKind.CSharp }]; + return [IntermediateNodeFactory.CSharpToken(content)]; } return node.FindDescendantNodes(); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs index 384d3110c83..9fb9f54d77e 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs @@ -31,9 +31,9 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte var attributeNode = new CSharpCodeIntermediateNode(); attributeNode.Children.AddRange([ - IntermediateToken.CreateCSharpToken($"[global::{ComponentsApi.LayoutAttribute.FullTypeName}(typeof("), - IntermediateToken.CreateCSharpToken(token.Content, documentNode.Options.DesignTime ? null : token.Source), - IntermediateToken.CreateCSharpToken("))]") + IntermediateNodeFactory.CSharpToken($"[global::{ComponentsApi.LayoutAttribute.FullTypeName}(typeof("), + IntermediateNodeFactory.CSharpToken(token.Content, documentNode.Options.DesignTime ? null : token.Source), + IntermediateNodeFactory.CSharpToken("))]") ]); // Insert the new attribute on top of the class diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRenderModeDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRenderModeDirectivePass.cs index 0c9b0a43c64..1d10ed831a3 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRenderModeDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRenderModeDirectivePass.cs @@ -47,53 +47,36 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte { Children = { - new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = $"private static global::{ComponentsApi.IComponentRenderMode.FullTypeName} ModeImpl => " - }, + IntermediateNodeFactory.CSharpToken($"private static global::{ComponentsApi.IComponentRenderMode.FullTypeName} ModeImpl => "), new CSharpCodeIntermediateNode() { Source = child.Source, Children = { child is not DirectiveTokenIntermediateNode directiveToken - ? child - : new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = directiveToken.Content - } + ? child + : IntermediateNodeFactory.CSharpToken(directiveToken.Content) } }, - new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = ";" - } + IntermediateNodeFactory.CSharpToken(";") } }); + classDecl.Children.Add(new CSharpCodeIntermediateNode() { Children = { - new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = $"public override global::{ComponentsApi.IComponentRenderMode.FullTypeName} Mode => ModeImpl;" - } + IntermediateNodeFactory.CSharpToken($"public override global::{ComponentsApi.IComponentRenderMode.FullTypeName} Mode => ModeImpl;") } }); + @class.Children.Add(classDecl); // generate the attribute usage on top of the class var attributeNode = new CSharpCodeIntermediateNode(); var namespaceSeparator = string.IsNullOrEmpty(@namespace.Content) ? string.Empty : "."; - attributeNode.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = $"[global::{@namespace.Content}{namespaceSeparator}{@class.ClassName}.{GeneratedRenderModeAttributeName}]", - }); + attributeNode.Children.Add( + IntermediateNodeFactory.CSharpToken($"[global::{@namespace.Content}{namespaceSeparator}{@class.ClassName}.{GeneratedRenderModeAttributeName}]")); // Insert the new attribute on top of the class var childCount = @namespace.Children.Count; @@ -105,6 +88,7 @@ child is not DirectiveTokenIntermediateNode directiveToken break; } } + Debug.Assert(@namespace.Children.Count == childCount + 1); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs index a15963d3a1f..9dbaf9bc6f3 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs @@ -102,7 +102,7 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE context.CodeWriter .Write(methodInvocation) .WriteParameterSeparator(); - + if (firstCSharpChild is not null) { context.CodeWriter.Write(firstCSharpChild.Content); @@ -151,7 +151,7 @@ public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext co // ... so to avoid losing whitespace, convert the prefix to a further token in the list if (!string.IsNullOrEmpty(node.Prefix)) { - _currentAttributeValues.Add(new IntermediateToken() { Kind = TokenKind.Html, Content = node.Prefix }); + _currentAttributeValues.Add(IntermediateNodeFactory.HtmlToken(node.Prefix)); } for (var i = 0; i < node.Children.Count; i++) @@ -306,7 +306,7 @@ public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlA } var stringContent = ((IntermediateToken)node.Children.Single()).Content; - _currentAttributeValues.Add(new IntermediateToken() { Kind = TokenKind.Html, Content = node.Prefix + stringContent, }); + _currentAttributeValues.Add(IntermediateNodeFactory.HtmlToken(node.Prefix + stringContent)); } public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node) @@ -1040,14 +1040,10 @@ protected override void WriteReferenceCaptureInnards(CodeRenderingContext contex { Source = node.Source, Children = - { - node.IdentifierToken, - new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = $" = {typecastIfNeeded}{refCaptureParamName};" - } - } + { + node.IdentifierToken, + IntermediateNodeFactory.CSharpToken( $" = {typecastIfNeeded}{refCaptureParamName};") + } }); } } @@ -1060,21 +1056,13 @@ public override void WriteRenderMode(CodeRenderingContext context, RenderModeInt { Children = { - new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = $"global::{ComponentsApi.IComponentRenderMode.FullTypeName} {_scopeStack.RenderModeVarName} = " - }, + IntermediateNodeFactory.CSharpToken($"global::{ComponentsApi.IComponentRenderMode.FullTypeName} {_scopeStack.RenderModeVarName} = "), new CSharpCodeIntermediateNode { Source = node.Source, Children = { node.Children[0] } }, - new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = ";" - } + IntermediateNodeFactory.CSharpToken(";") } }); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/AttributeDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/AttributeDirectivePass.cs index 83706a014a7..de07284da1a 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/AttributeDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/AttributeDirectivePass.cs @@ -28,12 +28,7 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte Source = token.Source }; - node.Children.Add(new IntermediateToken() - { - Content = token.Content, - Source = token.Source, - Kind = TokenKind.CSharp, - }); + node.Children.Add(IntermediateNodeFactory.CSharpToken(token.Content, token.Source)); @namespace.Children.Insert(classIndex++, node); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectivePass.cs index 48a8be75f7d..041d42f08eb 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectivePass.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Language.Intermediate; @@ -37,36 +35,17 @@ public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node { node.Children.Insert(0, new CSharpCodeIntermediateNode() { - Children = - { - new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "#pragma warning disable 0414", - } - } + Children = { IntermediateNodeFactory.CSharpToken("#pragma warning disable 0414") } }); + node.Children.Insert(1, new CSharpCodeIntermediateNode() { - Children = - { - new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = $"private static object {DesignTimeVariable} = null;", - } - } + Children = { IntermediateNodeFactory.CSharpToken($"private static object {DesignTimeVariable} = null;") } }); + node.Children.Insert(2, new CSharpCodeIntermediateNode() { - Children = - { - new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "#pragma warning restore 0414", - } - } + Children = { IntermediateNodeFactory.CSharpToken("#pragma warning restore 0414") } }); _directiveNodes.Push(new DesignTimeDirectiveIntermediateNode()); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/EliminateMethodBodyPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/EliminateMethodBodyPass.cs index 089dbd09688..6992b55857e 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/EliminateMethodBodyPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/EliminateMethodBodyPass.cs @@ -42,28 +42,16 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte documentNode.Children.Insert(documentNode.Children.IndexOf(documentNode.FindPrimaryNamespace()), new CSharpCodeIntermediateNode() { Children = - { - // Field is assigned but never used - new IntermediateToken() - { - Content = "#pragma warning disable 0414" + Environment.NewLine, - Kind = TokenKind.CSharp, - }, + { + // Field is assigned but never used + IntermediateNodeFactory.CSharpToken("#pragma warning disable 0414" + Environment.NewLine), - // Field is never assigned - new IntermediateToken() - { - Content = "#pragma warning disable 0649" + Environment.NewLine, - Kind = TokenKind.CSharp, - }, + // Field is never assigned + IntermediateNodeFactory.CSharpToken("#pragma warning disable 0649" + Environment.NewLine), - // Field is never used - new IntermediateToken() - { - Content = "#pragma warning disable 0169" + Environment.NewLine, - Kind = TokenKind.CSharp, - }, - }, + // Field is never used + IntermediateNodeFactory.CSharpToken("#pragma warning disable 0169" + Environment.NewLine) + } }); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ImplementsDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ImplementsDirectivePass.cs index c2392f2e76d..87a116d8e48 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ImplementsDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ImplementsDirectivePass.cs @@ -23,7 +23,7 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte if (token != null) { var source = codeDocument.ParserOptions.DesignTime ? null : token.Source; - @class.Interfaces.Add(IntermediateToken.CreateCSharpToken(token.Content, source)); + @class.Interfaces.Add(IntermediateNodeFactory.CSharpToken(token.Content, source)); } } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs index 6182ad3169f..adca9647a76 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs @@ -43,12 +43,7 @@ private void ProcessElement(HtmlContentIntermediateNode node, string cssScope, r { if (IsValidElement(token, previousToken)) { - node.Children.Insert(i + 1, new IntermediateToken() - { - Content = cssScope, - Kind = TokenKind.Html, - Source = null - }); + node.Children.Insert(i + 1, IntermediateNodeFactory.HtmlToken(cssScope)); i++; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/BaseTypeWithModel.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/BaseTypeWithModel.cs index 58deca2b6c5..1c62ec320ec 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/BaseTypeWithModel.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/BaseTypeWithModel.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; -public sealed class BaseTypeWithModel +public sealed class BaseTypeWithModel { const string ModelGenericParameter = ""; @@ -11,10 +11,10 @@ public BaseTypeWithModel(string baseType, SourceSpan? location = null) { if (baseType.EndsWith(ModelGenericParameter, System.StringComparison.Ordinal)) { - BaseType = IntermediateToken.CreateCSharpToken(baseType[0..^ModelGenericParameter.Length]); - GreaterThan = IntermediateToken.CreateCSharpToken("<"); - ModelType = IntermediateToken.CreateCSharpToken("TModel"); - LessThan = IntermediateToken.CreateCSharpToken(">"); + BaseType = IntermediateNodeFactory.CSharpToken(baseType[0..^ModelGenericParameter.Length]); + GreaterThan = IntermediateNodeFactory.CSharpToken("<"); + ModelType = IntermediateNodeFactory.CSharpToken("TModel"); + LessThan = IntermediateNodeFactory.CSharpToken(">"); if (location.HasValue) { @@ -27,7 +27,7 @@ public BaseTypeWithModel(string baseType, SourceSpan? location = null) } else { - BaseType = IntermediateToken.CreateCSharpToken(baseType, location); + BaseType = IntermediateNodeFactory.CSharpToken(baseType, location); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs index d156164d5d9..e63325adbf8 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs @@ -19,8 +19,6 @@ public class IntermediateToken : IntermediateNode public TokenKind Kind { get; set; } = TokenKind.Unknown; - public static IntermediateToken CreateCSharpToken(string content, SourceSpan? location = null) => new IntermediateToken() { Content = content, Kind = TokenKind.CSharp, Source = location }; - public override void Accept(IntermediateNodeVisitor visitor) { if (visitor == null) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/AssemblyAttributeInjectionPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/AssemblyAttributeInjectionPass.cs index 42e9c5532ea..335cdad5de2 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/AssemblyAttributeInjectionPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/AssemblyAttributeInjectionPass.cs @@ -1,10 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; @@ -67,16 +66,12 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte Debug.Assert(index >= 0); var pageAttribute = new CSharpCodeIntermediateNode(); - pageAttribute.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = attribute, - }); + pageAttribute.Children.Add(IntermediateNodeFactory.CSharpToken(attribute)); documentNode.Children.Insert(index, pageAttribute); } - private static string MakeVerbatimStringLiteral(string value) + private static string MakeVerbatimStringLiteral(string? value) { if (value == null) { @@ -87,7 +82,8 @@ private static string MakeVerbatimStringLiteral(string value) return $"@\"{value}\""; } - private static string ConvertToViewEnginePath(string relativePath) + [return: NotNullIfNotNull(nameof(relativePath))] + private static string? ConvertToViewEnginePath(string? relativePath) { if (string.IsNullOrEmpty(relativePath)) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/InstrumentationPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/InstrumentationPass.cs index a15d7512f3a..bcc4da99b9e 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/InstrumentationPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/InstrumentationPass.cs @@ -39,24 +39,16 @@ private static void AddInstrumentation(InstrumentationItem item) var endContextMethodName = "EndContext"; // ORIGINAL: EndContextMethodName var beginNode = new CSharpCodeIntermediateNode(); - beginNode.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = string.Format( - CultureInfo.InvariantCulture, - "{0}({1}, {2}, {3});", - beginContextMethodName, - item.Source.AbsoluteIndex.ToString(CultureInfo.InvariantCulture), - item.Source.Length.ToString(CultureInfo.InvariantCulture), - item.IsLiteral ? "true" : "false") - }); + + var absoluteIndex = item.Source.AbsoluteIndex.ToString(CultureInfo.InvariantCulture); + var length = item.Source.Length.ToString(CultureInfo.InvariantCulture); + var isLiteral = item.IsLiteral ? "true" : "false"; + + beginNode.Children.Add( + IntermediateNodeFactory.CSharpToken($"{beginContextMethodName}({absoluteIndex}, {length}, {isLiteral});")); var endNode = new CSharpCodeIntermediateNode(); - endNode.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = string.Format(CultureInfo.InvariantCulture, "{0}();", endContextMethodName) - }); + endNode.Children.Add(IntermediateNodeFactory.CSharpToken($"{endContextMethodName}();")); var nodeIndex = item.Parent.Children.IndexOf(item.Node); item.Parent.Children.Insert(nodeIndex, beginNode); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/PagesPropertyInjectionPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/PagesPropertyInjectionPass.cs index fea140433f6..733311e0b55 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/PagesPropertyInjectionPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc.Version2_X/PagesPropertyInjectionPass.cs @@ -25,19 +25,12 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte var viewDataType = $"global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<{modelType}>"; var vddProperty = new CSharpCodeIntermediateNode(); - vddProperty.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = $"public {viewDataType} ViewData => ({viewDataType})PageContext?.ViewData;", - }); + vddProperty.Children.Add( + IntermediateNodeFactory.CSharpToken($"public {viewDataType} ViewData => ({viewDataType})PageContext?.ViewData;")); @class.Children.Add(vddProperty); var modelProperty = new CSharpCodeIntermediateNode(); - modelProperty.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = $"public {modelType} Model => ViewData.Model;", - }); + modelProperty.Children.Add(IntermediateNodeFactory.CSharpToken($"public {modelType} Model => ViewData.Model;")); @class.Children.Add(modelProperty); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelDirective.cs index c6ee849285e..f7f7fbdc944 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelDirective.cs @@ -58,17 +58,17 @@ private static IntermediateToken GetModelType(DocumentIntermediateNode document, var tokens = directive.Tokens.ToArray(); if (tokens.Length >= 1) { - return IntermediateToken.CreateCSharpToken(tokens[0].Content, tokens[0].Source); + return IntermediateNodeFactory.CSharpToken(tokens[0].Content, tokens[0].Source); } } if (document.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind) { - return IntermediateToken.CreateCSharpToken(visitor.Class.ClassName); + return IntermediateNodeFactory.CSharpToken(visitor.Class.ClassName); } else { - return IntermediateToken.CreateCSharpToken("dynamic"); + return IntermediateNodeFactory.CSharpToken("dynamic"); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs index 8679fd3073f..68642581bc6 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs @@ -1,11 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System; using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; @@ -40,21 +37,13 @@ public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode no { var expression = new CSharpExpressionIntermediateNode(); - expression.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "ModelExpressionProvider.CreateModelExpression(ViewData, __model => ", - }); + expression.Children.Add(IntermediateNodeFactory.CSharpToken("ModelExpressionProvider.CreateModelExpression(ViewData, __model => ")); if (node.Children.Count == 1 && node.Children[0] is IntermediateToken token && token.IsCSharp) { // A 'simple' expression will look like __model => __model.Foo - expression.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = "__model." - }); + expression.Children.Add(IntermediateNodeFactory.CSharpToken("__model.")); expression.Children.Add(token); } @@ -66,10 +55,10 @@ public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode no { for (var j = 0; j < nestedExpression.Children.Count; j++) { - if (nestedExpression.Children[j] is IntermediateToken cSharpToken && - cSharpToken.IsCSharp) + if (nestedExpression.Children[j] is IntermediateToken csharpToken && + csharpToken.IsCSharp) { - expression.Children.Add(cSharpToken); + expression.Children.Add(csharpToken); } } @@ -78,11 +67,7 @@ public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode no } } - expression.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = ")", - }); + expression.Children.Add(IntermediateNodeFactory.CSharpToken(")")); node.Children.Clear(); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PagesPropertyInjectionPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PagesPropertyInjectionPass.cs index d9d8d63f6b6..cf3a102c772 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PagesPropertyInjectionPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PagesPropertyInjectionPass.cs @@ -29,21 +29,15 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte var viewDataType = $"global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<{modelType.Content}>"; var vddProperty = new CSharpCodeIntermediateNode(); - vddProperty.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = nullableEnable(nullableEnabled, $"public {viewDataType} ViewData => ({viewDataType})PageContext?.ViewData"), - }); + vddProperty.Children.Add( + IntermediateNodeFactory.CSharpToken(nullableEnable(nullableEnabled, $"public {viewDataType} ViewData => ({viewDataType})PageContext?.ViewData"))); @class.Children.Add(vddProperty); if (codeDocument.CodeGenerationOptions.DesignTime || !razor9OrHigher) { var modelProperty = new CSharpCodeIntermediateNode(); - modelProperty.Children.Add(new IntermediateToken() - { - Kind = TokenKind.CSharp, - Content = nullableEnable(nullableEnabled, $"public {modelType.Content} Model => ViewData.Model"), - }); + modelProperty.Children.Add( + IntermediateNodeFactory.CSharpToken(nullableEnable(nullableEnabled, $"public {modelType.Content} Model => ViewData.Model"))); @class.Children.Add(modelProperty); } else diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor/test/GenericTypeNameRewriterTest.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor/test/GenericTypeNameRewriterTest.cs index 89ee278c6c1..967296692d2 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor/test/GenericTypeNameRewriterTest.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor/test/GenericTypeNameRewriterTest.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System.Collections.Generic; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; @@ -70,11 +68,11 @@ public void GenericTypeNameRewriter_CanReplaceTypeParametersWithTypeArguments(st { // Arrange var visitor = new GenericTypeNameRewriter(new Dictionary() - { - { "TItem1", new ComponentTypeArgumentIntermediateNode(new() { Children = { IntermediateToken.CreateCSharpToken("Type1") } })}, - { "TItem2", new ComponentTypeArgumentIntermediateNode(new() { Children = { IntermediateToken.CreateCSharpToken("Type2") } })}, - { "TItem3", new ComponentTypeArgumentIntermediateNode(new() { Children = { IntermediateToken.CreateCSharpToken(null) } })}, - }); + { + { "TItem1", new ComponentTypeArgumentIntermediateNode(new() { Children = { IntermediateNodeFactory.CSharpToken("Type1") } })}, + { "TItem2", new ComponentTypeArgumentIntermediateNode(new() { Children = { IntermediateNodeFactory.CSharpToken("Type2") } })}, + { "TItem3", new ComponentTypeArgumentIntermediateNode(new() { Children = { IntermediateNodeFactory.CSharpToken(null!) } })}, + }); // Act var actual = visitor.Rewrite(original); From 172d4e30b0282f7795d11bc9b44bf77988593253 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 2 Jul 2025 16:51:37 -0700 Subject: [PATCH 03/10] Replace calls to LazyIntermedateToken ctor with node factory methods --- ...faultRazorIntermediateNodeLoweringPhase.cs | 154 +++++++----------- 1 file changed, 59 insertions(+), 95 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs index 7f5dfe59ce7..49621df0824 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -758,13 +758,10 @@ public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValu Source = BuildSourceSpanFromNode(node), }); - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((MarkupLiteralAttributeValueSyntax)node).Value?.GetContent() ?? string.Empty, - Kind = TokenKind.Html, - Source = BuildSourceSpanFromNode(node.Value) - }); + _builder.Add(IntermediateNodeFactory.HtmlToken( + factoryArgument: node, + contentFactory: static node => ((MarkupLiteralAttributeValueSyntax)node).Value?.GetContent() ?? string.Empty, + source: BuildSourceSpanFromNode(node.Value))); _builder.Pop(); } @@ -897,13 +894,10 @@ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax return; } - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), - Kind = TokenKind.CSharp, - Source = BuildSourceSpanFromNode(node), - }); + _builder.Add(IntermediateNodeFactory.CSharpToken( + factoryArgument: node, + contentFactory: static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), + source: BuildSourceSpanFromNode(node))); base.VisitCSharpExpressionLiteral(node); } @@ -923,13 +917,10 @@ public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax no _builder.Push(statementNode); } - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((CSharpStatementLiteralSyntax)node).GetContent(), - Kind = TokenKind.CSharp, - Source = BuildSourceSpanFromNode(node), - }); + _builder.Add(IntermediateNodeFactory.CSharpToken( + factoryArgument: node, + contentFactory: static node => ((CSharpStatementLiteralSyntax)node).GetContent(), + source: BuildSourceSpanFromNode(node))); if (!isAttributeValue) { @@ -1020,13 +1011,10 @@ private void VisitHtmlContent(SyntaxNode node) _builder.Push(contentNode); - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((SyntaxNode)node).GetContent(), - Kind = TokenKind.Html, - Source = source, - }); + _builder.Add(IntermediateNodeFactory.HtmlToken( + factoryArgument: node, + contentFactory: static node => ((SyntaxNode)node).GetContent(), + source)); _builder.Pop(); } @@ -1262,13 +1250,10 @@ private void VisitAttributeValue(SyntaxNode node) private void Combine(HtmlContentIntermediateNode node, SyntaxNode item) { - node.Children.Add(new LazyIntermediateToken() - { - FactoryArgument = item, - ContentFactory = static item => ((SyntaxNode)item).GetContent(), - Kind = TokenKind.Html, - Source = BuildSourceSpanFromNode(item), - }); + node.Children.Add(IntermediateNodeFactory.HtmlToken( + factoryArgument: item, + contentFactory: static item => ((SyntaxNode)item).GetContent(), + source: BuildSourceSpanFromNode(item))); if (node.Source is SourceSpan source) { @@ -1504,13 +1489,10 @@ public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValu Source = BuildSourceSpanFromNode(node), }); - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((MarkupLiteralAttributeValueSyntax)node).Value?.GetContent() ?? string.Empty, - Kind = TokenKind.Html, - Source = BuildSourceSpanFromNode(node.Value) - }); + _builder.Add(IntermediateNodeFactory.HtmlToken( + factoryArgument: node, + contentFactory: static node => ((MarkupLiteralAttributeValueSyntax)node).Value?.GetContent() ?? string.Empty, + source: BuildSourceSpanFromNode(node.Value))); _builder.Pop(); } @@ -1526,13 +1508,10 @@ public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node) Source = BuildSourceSpanFromNode(node), }); - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((MarkupTextLiteralSyntax)node).GetContent() ?? string.Empty, - Kind = TokenKind.Html, - Source = BuildSourceSpanFromNode(node), - }); + _builder.Add(IntermediateNodeFactory.HtmlToken( + factoryArgument: node, + contentFactory: static node => ((MarkupTextLiteralSyntax)node).GetContent() ?? string.Empty, + source: BuildSourceSpanFromNode(node))); _builder.Pop(); @@ -1577,15 +1556,12 @@ public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node) { Source = source, Children = - { - new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((MarkupTextLiteralSyntax)node).GetContent(), - Kind = TokenKind.Html, - Source = source, - } - } + { + IntermediateNodeFactory.HtmlToken( + factoryArgument: node, + contentFactory: static node => ((MarkupTextLiteralSyntax)node).GetContent(), + source) + } }); } @@ -1754,13 +1730,10 @@ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax return; } - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), - Kind = TokenKind.CSharp, - Source = BuildSourceSpanFromNode(node), - }); + _builder.Add(IntermediateNodeFactory.CSharpToken( + factoryArgument: node, + contentFactory: static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), + source: BuildSourceSpanFromNode(node))); base.VisitCSharpExpressionLiteral(node); } @@ -1780,13 +1753,10 @@ public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax no _builder.Push(statementNode); } - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((CSharpStatementLiteralSyntax)node).GetContent(), - Kind = TokenKind.CSharp, - Source = BuildSourceSpanFromNode(node), - }); + _builder.Add(IntermediateNodeFactory.CSharpToken( + factoryArgument: node, + contentFactory: static node => ((CSharpStatementLiteralSyntax)node).GetContent(), + source: BuildSourceSpanFromNode(node))); if (!isAttributeValue) { @@ -2234,26 +2204,23 @@ private void VisitAttributeValue(SyntaxNode node) private void Combine(HtmlContentIntermediateNode node, SyntaxNode item) { - node.Children.Add(new LazyIntermediateToken() - { - FactoryArgument = item, - ContentFactory = static item => ((SyntaxNode)item).GetContent(), - Kind = TokenKind.Html, - Source = BuildSourceSpanFromNode(item), - }); + node.Children.Add(IntermediateNodeFactory.HtmlToken( + factoryArgument: item, + contentFactory: static item => ((SyntaxNode)item).GetContent(), + source: BuildSourceSpanFromNode(item))); - if (node.Source != null) + if (node.Source is SourceSpan source) { - Debug.Assert(node.Source.Value.FilePath != null); + Debug.Assert(source.FilePath != null); node.Source = new SourceSpan( - node.Source.Value.FilePath, - node.Source.Value.AbsoluteIndex, - node.Source.Value.LineIndex, - node.Source.Value.CharacterIndex, - node.Source.Value.Length + item.Width, - node.Source.Value.LineCount, - node.Source.Value.EndCharacterIndex); + source.FilePath, + source.AbsoluteIndex, + source.LineIndex, + source.CharacterIndex, + source.Length + item.Width, + source.LineCount, + source.EndCharacterIndex); } } } @@ -2360,13 +2327,10 @@ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax return; } - _builder.Add(new LazyIntermediateToken() - { - FactoryArgument = node, - ContentFactory = static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), - Kind = TokenKind.CSharp, - Source = BuildSourceSpanFromNode(node), - }); + _builder.Add(IntermediateNodeFactory.CSharpToken( + factoryArgument: node, + contentFactory: static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), + source: BuildSourceSpanFromNode(node))); } public override void VisitCSharpStatement(CSharpStatementSyntax node) From 2ed62daa358a4a2bfc749c19d10cfa0c4d7c4ba0 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 7 Jul 2025 12:28:51 -0700 Subject: [PATCH 04/10] Clean up IntermediateToken and LazyIntermediateToken Basic clean up: - Enable nullability - Use constructors rather than property setters Note: The uses of the ! operator will be cleaned up in a later commit. --- .../ComponentDesignTimeNodeWriter.cs | 8 ++--- .../ComponentEventHandlerLoweringPass.cs | 2 +- .../Components/ComponentMarkupEncodingPass.cs | 2 +- .../Components/ComponentRuntimeNodeWriter.cs | 6 ++-- .../Intermediate/IntermediateNodeFactory.cs | 8 ++--- .../Intermediate/IntermediateNodeFormatter.cs | 2 +- .../Intermediate/IntermediateToken.cs | 30 +++++++++++-------- .../Intermediate/LazyIntermediateToken.cs | 23 ++++++++------ 8 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs index 36cd2423d9e..09ccb5be37e 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs @@ -135,7 +135,7 @@ private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpEx if (node.Children[i] is IntermediateToken token && token.IsCSharp) { context.AddSourceMappingFor(token); - context.CodeWriter.Write(token.Content); + context.CodeWriter.Write(token.Content!); } else { @@ -154,7 +154,7 @@ private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpEx { if (node.Children[i] is IntermediateToken token && token.IsCSharp) { - context.CodeWriter.Write(token.Content); + context.CodeWriter.Write(token.Content!); } else { @@ -210,7 +210,7 @@ public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeInt if (node.Children[i] is IntermediateToken token && token.IsCSharp) { context.AddSourceMappingFor(token); - context.CodeWriter.Write(token.Content); + context.CodeWriter.Write(token.Content!); } else { @@ -352,7 +352,7 @@ protected override void BeginWriteAttribute(CodeRenderingContext context, Interm var tokens = GetCSharpTokens(expression); for (var i = 0; i < tokens.Count; i++) { - context.CodeWriter.Write(tokens[i].Content); + context.CodeWriter.Write(tokens[i].Content!); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs index 28dbff3e4f4..514664291c6 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs @@ -245,7 +245,7 @@ private static ImmutableArray GetAttributeContent(Intermediat // an expression. var tokens = htmlContentNode.FindDescendantNodes(); - var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content.Replace("\"", "\\\""))) + "\""; + var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content!.Replace("\"", "\\\""))) + "\""; return [IntermediateNodeFactory.CSharpToken(content)]; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs index b01ccb09401..5c87842465a 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs @@ -140,7 +140,7 @@ public override void VisitHtml(HtmlContentIntermediateNode node) continue; } - token.Content = decodedContent[i]; + token.UpdateContent(decodedContent[i]); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs index 9dbaf9bc6f3..9360993db40 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs @@ -105,7 +105,7 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE if (firstCSharpChild is not null) { - context.CodeWriter.Write(firstCSharpChild.Content); + context.CodeWriter.Write(firstCSharpChild.Content!); } } @@ -1231,13 +1231,13 @@ private static void WriteCSharpToken(CodeRenderingContext context, IntermediateT { if (token.Source?.FilePath == null) { - context.CodeWriter.Write(token.Content); + context.CodeWriter.Write(token.Content!); return; } using (context.CodeWriter.BuildEnhancedLinePragma(token.Source, context)) { - context.CodeWriter.Write(token.Content); + context.CodeWriter.Write(token.Content!); } } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs index 942b830b408..697ae51c259 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs @@ -8,14 +8,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; internal static class IntermediateNodeFactory { public static IntermediateToken CSharpToken(string content, SourceSpan? source = null) - => new() { Content = content, Kind = TokenKind.CSharp, Source = source }; + => new(TokenKind.CSharp, content, source); public static LazyIntermediateToken CSharpToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new() { FactoryArgument = factoryArgument, ContentFactory = contentFactory, Kind = TokenKind.CSharp, Source = source }; + => new(TokenKind.CSharp, factoryArgument, contentFactory, source); public static IntermediateToken HtmlToken(string content, SourceSpan? source = null) - => new() { Content = content, Kind = TokenKind.Html, Source = source }; + => new(TokenKind.Html, content, source); public static LazyIntermediateToken HtmlToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new() { FactoryArgument = factoryArgument, ContentFactory = contentFactory, Kind = TokenKind.Html, Source = source }; + => new(TokenKind.Html, factoryArgument, contentFactory, source); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs index 39d0a46c106..a2ba8b62493 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs @@ -71,7 +71,7 @@ public void WriteChildren(IntermediateNodeCollection children) { if (child is IntermediateToken token) { - WriteEscaped(token.Content); + WriteEscaped(token.Content!); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs index e63325adbf8..e2fa5902374 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs @@ -1,34 +1,38 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - -using System; - namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public class IntermediateToken : IntermediateNode { - public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; - - public virtual string Content { get; set; } + public TokenKind Kind { get; } public bool IsCSharp => Kind == TokenKind.CSharp; - public bool IsHtml => Kind == TokenKind.Html; - public TokenKind Kind { get; set; } = TokenKind.Unknown; + public virtual string? Content { get; private set; } - public override void Accept(IntermediateNodeVisitor visitor) + public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; + + public IntermediateToken(TokenKind kind, string? content, SourceSpan? source) { - if (visitor == null) + Kind = kind; + Content = content; + + if (source != null) { - throw new ArgumentNullException(nameof(visitor)); + Source = source; } + } - visitor.VisitToken(this); + public void UpdateContent(string content) + { + Content = content; } + public override void Accept(IntermediateNodeVisitor visitor) + => visitor.VisitToken(this); + public override void FormatNode(IntermediateNodeFormatter formatter) { formatter.WriteContent(Content); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs index a517bc764d0..bf817b834b3 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs @@ -1,25 +1,30 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System; namespace Microsoft.AspNetCore.Razor.Language.Intermediate; -internal class LazyIntermediateToken : IntermediateToken +internal sealed class LazyIntermediateToken( + TokenKind kind, + object factoryArgument, + Func contentFactory, + SourceSpan? source) + : IntermediateToken(kind, content: null, source) { - public object FactoryArgument { get; set; } - public Func ContentFactory { get; set; } + private object _factoryArgument = factoryArgument; + private Func _contentFactory = contentFactory; - public override string Content + public override string? Content { get { - if (base.Content == null && ContentFactory != null) + if (base.Content == null && _contentFactory != null) { - Content = ContentFactory(FactoryArgument); - ContentFactory = null; + UpdateContent(_contentFactory(_factoryArgument)); + + _factoryArgument = null!; + _contentFactory = null!; } return base.Content; From 55643106eef2ac75f7374fd0c26cfbfdaa663938 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 7 Jul 2025 12:49:13 -0700 Subject: [PATCH 05/10] Merge IntermediateToken and LazyIntermediateToken Introduce a `LazyContent` helper class that can be used to define content that is computed lazily. Use this helper class to allow `IntermediateToken` to directly support lazy content and remove `LazyIntermediateToken`. NOTE: There are some slight shenanigans to avoid updating test base lines due to `LazyIntermediateToken` being removed. --- .../test/Intermediate/LazyContentTests.cs | 98 +++++++++++++++++++ .../Intermediate/IntermediateNodeFactory.cs | 8 +- .../Intermediate/IntermediateToken.cs | 26 ++++- .../src/Language/Intermediate/LazyContent.cs | 85 ++++++++++++++++ .../Intermediate/LazyIntermediateToken.cs | 33 ------- .../IntermediateNodeWriter.cs | 6 +- 6 files changed, 214 insertions(+), 42 deletions(-) create mode 100644 src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Intermediate/LazyContentTests.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs delete mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Intermediate/LazyContentTests.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Intermediate/LazyContentTests.cs new file mode 100644 index 00000000000..efa711f5e93 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Intermediate/LazyContentTests.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate; + +public class LazyContentTests +{ + [Fact] + public void Create_ThrowsIfFactoryIsNull() + { + // Arrange, Act & Assert + Assert.Throws(() => LazyContent.Create("x", null!)); + } + + [Fact] + public void Value_ReturnsContentFromFactory() + { + // Arrange + var args = (x: 19, y: 23); + var lazy = LazyContent.Create(args, static arg => (arg.x + arg.y).ToString()); + + // Act + var content = lazy.Value; + + // Assert + Assert.NotNull(content); + Assert.Equal("42", content); + } + + [Fact] + public void Value_InvokesFactoryOnlyOnce() + { + // Arrange + var callCount = 0; + var text = "test"; + + var lazy = LazyContent.Create(text, arg => + { + Interlocked.Increment(ref callCount); + return arg; + }); + + // Act + var value1 = lazy.Value; + var value2 = lazy.Value; + + // Assert + Assert.Equal(value1, value2); + Assert.Equal(1, callCount); + } + + [Fact] + public void Value_IsThreadSafe() + { + const int ThreadCount = 20; + + // Arrange + var callCount = 0; + + var lazy = LazyContent.Create("thread", arg => + { + Interlocked.Increment(ref callCount); + Thread.Sleep(10); // Simulate work + + return arg; + }); + + var threads = new Thread[ThreadCount]; + var results = new string?[ThreadCount]; + + // Act + for (var i = 0; i < threads.Length; i++) + { + var idx = i; // Capture the current index + + threads[i] = new Thread(() => results[idx] = lazy.Value); + threads[i].Start(); + } + + foreach (var thread in threads) + { + thread.Join(); + } + + // Assert that all of the results are equal. + for (var i = 1; i < results.Length; i++) + { + Assert.Equal(results[0], results[i]); + } + + // Assert that the factory was called only once. + Assert.Equal(1, callCount); + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs index 697ae51c259..fb7d17c83e5 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs @@ -10,12 +10,12 @@ internal static class IntermediateNodeFactory public static IntermediateToken CSharpToken(string content, SourceSpan? source = null) => new(TokenKind.CSharp, content, source); - public static LazyIntermediateToken CSharpToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new(TokenKind.CSharp, factoryArgument, contentFactory, source); + public static IntermediateToken CSharpToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) + => new(TokenKind.CSharp, LazyContent.Create(factoryArgument, contentFactory), source); public static IntermediateToken HtmlToken(string content, SourceSpan? source = null) => new(TokenKind.Html, content, source); - public static LazyIntermediateToken HtmlToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new(TokenKind.Html, factoryArgument, contentFactory, source); + public static IntermediateToken HtmlToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) + => new(TokenKind.Html, LazyContent.Create(factoryArgument, contentFactory), source); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs index e2fa5902374..ef8993f47fb 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs @@ -3,21 +3,39 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; -public class IntermediateToken : IntermediateNode +public sealed class IntermediateToken : IntermediateNode { public TokenKind Kind { get; } public bool IsCSharp => Kind == TokenKind.CSharp; public bool IsHtml => Kind == TokenKind.Html; - public virtual string? Content { get; private set; } + public bool IsLazy { get; } + + private object? _content; + + public string? Content + => _content is LazyContent lazy ? lazy.Value : (string?)_content; public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; public IntermediateToken(TokenKind kind, string? content, SourceSpan? source) { Kind = kind; - Content = content; + _content = content; + IsLazy = false; + + if (source != null) + { + Source = source; + } + } + + internal IntermediateToken(TokenKind kind, LazyContent content, SourceSpan? source) + { + Kind = kind; + _content = content; + IsLazy = true; if (source != null) { @@ -27,7 +45,7 @@ public IntermediateToken(TokenKind kind, string? content, SourceSpan? source) public void UpdateContent(string content) { - Content = content; + _content = content; } public override void Accept(IntermediateNodeVisitor visitor) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs new file mode 100644 index 00000000000..f92e01abd4a --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Threading; + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate; + +internal abstract class LazyContent +{ + private LazyContent() + { + } + + public abstract string? Value { get; } + + public static LazyContent Create(T arg, Func contentFactory) + { + ArgHelper.ThrowIfNull(contentFactory); + + return new LazyContentImpl(arg, contentFactory); + } + + private sealed class LazyContentImpl(T arg, Func contentFactory) : LazyContent + { + private const int Uninitialized = 0; + private const int Computing = 1; + private const int Initialized = 2; + + private T _arg = arg; + private Func _contentFactory = contentFactory; + + private string? _value; + private int _state; + + public override string? Value + { + get + { + // Return the value if it has already been initialized; otherwise, compute it. + return Volatile.Read(ref _state) == Initialized + ? _value + : GetOrComputeAndStoreValue(); + } + } + + private string? GetOrComputeAndStoreValue() + { + SpinWait spinner = default; + + while (true) + { + switch (Interlocked.CompareExchange(ref _state, Computing, Uninitialized)) + { + case Uninitialized: + Debug.Assert(_contentFactory is not null, "Content factory should not be null at this point."); + + // This thread gets to compute the value and clear the references for GC. + _value = _contentFactory(_arg); + + _arg = default!; + _contentFactory = null!; + + Volatile.Write(ref _state, Initialized); + + return _value; + + case Computing: + // Another thread is already computing the value, wait for it to finish. + spinner.SpinOnce(); + + continue; + + case Initialized: + // The value has been initialized by another thread. Return it! + return _value; + + default: + return Assumed.Unreachable(); + } + } + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs deleted file mode 100644 index bf817b834b3..00000000000 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyIntermediateToken.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace Microsoft.AspNetCore.Razor.Language.Intermediate; - -internal sealed class LazyIntermediateToken( - TokenKind kind, - object factoryArgument, - Func contentFactory, - SourceSpan? source) - : IntermediateToken(kind, content: null, source) -{ - private object _factoryArgument = factoryArgument; - private Func _contentFactory = contentFactory; - - public override string? Content - { - get - { - if (base.Content == null && _contentFactory != null) - { - UpdateContent(_contentFactory(_factoryArgument)); - - _factoryArgument = null!; - _contentFactory = null!; - } - - return base.Content; - } - } -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs index f32f3f31020..f59dfea40cc 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs @@ -21,7 +21,6 @@ public class IntermediateNodeWriter : IntermediateNodeVisitor, IExtensionIntermediateNodeVisitor, IExtensionIntermediateNodeVisitor - { private readonly TextWriter _writer; @@ -284,10 +283,15 @@ protected void WriteNewLine() protected void WriteName(IntermediateNode node) { var typeName = node.GetType().Name; + if (typeName.EndsWith("IntermediateNode", StringComparison.Ordinal)) { _writer.Write(typeName[..^"IntermediateNode".Length]); } + else if (node is IntermediateToken token) + { + _writer.Write(token.IsLazy ? "LazyIntermediateToken" : "IntermediateToken"); + } else { _writer.Write(typeName); From a0de6cf96b07e2fcce39bdd611a97fe18a4bc247 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 7 Jul 2025 12:53:34 -0700 Subject: [PATCH 06/10] Introduce CSharpIntermediateToken and HtmlIntermediateToken Make `IntermediateToken` abstract and add strongly-typed `CSharpIntermediateToken` and `HtmlIntermediateToken`. --- .../Intermediate/CSharpIntermediateToken.cs | 17 +++++++++++++++++ .../Intermediate/HtmlIntermediateToken.cs | 17 +++++++++++++++++ .../Intermediate/IntermediateNodeFactory.cs | 16 ++++++++-------- .../Language/Intermediate/IntermediateToken.cs | 6 +++--- 4 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs new file mode 100644 index 00000000000..e8310b62c8d --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate; + +public sealed class CSharpIntermediateToken : IntermediateToken +{ + public CSharpIntermediateToken(string? content, SourceSpan? source) + : base(TokenKind.CSharp, content, source) + { + } + + internal CSharpIntermediateToken(LazyContent content, SourceSpan? source) + : base(TokenKind.CSharp, content, source) + { + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs new file mode 100644 index 00000000000..39eda3b6aea --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate; + +public sealed class HtmlIntermediateToken : IntermediateToken +{ + public HtmlIntermediateToken(string? content, SourceSpan? source) + : base(TokenKind.Html, content, source) + { + } + + internal HtmlIntermediateToken(LazyContent content, SourceSpan? source) + : base(TokenKind.Html, content, source) + { + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs index fb7d17c83e5..1ea061c19d8 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs @@ -7,15 +7,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; internal static class IntermediateNodeFactory { - public static IntermediateToken CSharpToken(string content, SourceSpan? source = null) - => new(TokenKind.CSharp, content, source); + public static CSharpIntermediateToken CSharpToken(string content, SourceSpan? source = null) + => new(content, source); - public static IntermediateToken CSharpToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new(TokenKind.CSharp, LazyContent.Create(factoryArgument, contentFactory), source); + public static CSharpIntermediateToken CSharpToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) + => new(LazyContent.Create(factoryArgument, contentFactory), source); - public static IntermediateToken HtmlToken(string content, SourceSpan? source = null) - => new(TokenKind.Html, content, source); + public static HtmlIntermediateToken HtmlToken(string content, SourceSpan? source = null) + => new(content, source); - public static IntermediateToken HtmlToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new(TokenKind.Html, LazyContent.Create(factoryArgument, contentFactory), source); + public static HtmlIntermediateToken HtmlToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) + => new(LazyContent.Create(factoryArgument, contentFactory), source); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs index ef8993f47fb..f0b6c62f05f 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; -public sealed class IntermediateToken : IntermediateNode +public abstract class IntermediateToken : IntermediateNode { public TokenKind Kind { get; } @@ -19,7 +19,7 @@ public string? Content public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; - public IntermediateToken(TokenKind kind, string? content, SourceSpan? source) + protected IntermediateToken(TokenKind kind, string? content, SourceSpan? source) { Kind = kind; _content = content; @@ -31,7 +31,7 @@ public IntermediateToken(TokenKind kind, string? content, SourceSpan? source) } } - internal IntermediateToken(TokenKind kind, LazyContent content, SourceSpan? source) + private protected IntermediateToken(TokenKind kind, LazyContent content, SourceSpan? source) { Kind = kind; _content = content; From 43e2957a652b3c0638c661bcaf1162d563a16bc8 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 7 Jul 2025 13:20:24 -0700 Subject: [PATCH 07/10] Remove TokenKind and IntermedateToken's Kind, IsCSharp, and IsHtml Update each reference to these properties with type tests for CSharpIntermediateToken or HtmlIntermediateToken. --- .../test/ModelExpressionPassTest.cs | 9 +- .../AssemblyAttributeInjectionPassTest.cs | 12 +- .../test/ModelExpressionPassTest.cs | 9 +- .../test/ModelExpressionPassTest.cs | 9 +- .../ComponentMarkupEncodingPassTest.cs | 4 +- .../test/Components/NodeAssert.cs | 12 +- .../DefaultTagHelperDescriptorFactory.cs | 1 - .../CodeGeneration/DesignTimeNodeWriter.cs | 34 +-- .../CodeGeneration/RuntimeNodeWriter.cs | 6 +- .../ComponentDesignTimeNodeWriter.cs | 125 ++++----- .../Components/ComponentGenericTypePass.cs | 4 +- .../Components/ComponentMarkupBlockPass.cs | 2 +- .../Components/ComponentMarkupEncodingPass.cs | 6 +- .../Components/ComponentRuntimeNodeWriter.cs | 249 +++++++++--------- .../Components/ComponentSplatLoweringPass.cs | 2 +- .../DefaultTagHelperTargetExtension.cs | 10 +- ...catedTagHelperAttributeOptimizationPass.cs | 2 +- .../Language/Extensions/ViewCssScopePass.cs | 2 +- .../Intermediate/CSharpIntermediateToken.cs | 4 +- .../ComponentTypeArgumentIntermediateNode.cs | 8 +- .../Intermediate/HtmlIntermediateToken.cs | 4 +- .../Intermediate/IntermediateToken.cs | 11 +- .../src/Language/Intermediate/TokenKind.cs | 13 - .../src/Mvc/ModelExpressionPass.cs | 5 +- .../IntermediateNodeWriter.cs | 9 +- .../Intermediate/IntermediateNodeAssert.cs | 21 +- .../IntermediateNodeExtensions.cs | 2 +- 27 files changed, 246 insertions(+), 329 deletions(-) delete mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/TokenKind.cs diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/test/ModelExpressionPassTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/test/ModelExpressionPassTest.cs index 666c353a552..d8b49f683ea 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/test/ModelExpressionPassTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/test/ModelExpressionPassTest.cs @@ -51,8 +51,7 @@ public void ModelExpressionPass_NonModelExpressionProperty_Ignored() var tagHelperNode = documentNode.GetTagHelperNode(); var setProperty = tagHelperNode.Children.OfType().Single(); - var token = Assert.IsAssignableFrom(Assert.Single(setProperty.Children)); - Assert.True(token.IsCSharp); + var token = Assert.IsAssignableFrom(Assert.Single(setProperty.Children)); Assert.Equal("17", token.Content); } @@ -86,8 +85,7 @@ public void ModelExpressionPass_ModelExpressionProperty_SimpleExpression() var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => __model.Bar)", expression.GetCSharpContent()); - var originalNode = Assert.IsAssignableFrom(expression.Children[2]); - Assert.Equal(TokenKind.CSharp, originalNode.Kind); + var originalNode = Assert.IsAssignableFrom(expression.Children[2]); Assert.Equal("Bar", originalNode.Content); var source = Assert.NotNull(originalNode.Source); Assert.Equal(new SourceSpan("test.cshtml", 51, 1, 8, 3), source); @@ -123,8 +121,7 @@ public void ModelExpressionPass_ModelExpressionProperty_ComplexExpression() var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => Bar)", expression.GetCSharpContent()); - var originalNode = Assert.IsAssignableFrom(expression.Children[1]); - Assert.Equal(TokenKind.CSharp, originalNode.Kind); + var originalNode = Assert.IsAssignableFrom(expression.Children[1]); Assert.Equal("Bar", originalNode.Content); var source = Assert.NotNull(originalNode.Source); Assert.Equal(new SourceSpan("test.cshtml", 52, 1, 9, 3), source); diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/AssemblyAttributeInjectionPassTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/AssemblyAttributeInjectionPassTest.cs index 364471cef82..73addfc7866 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/AssemblyAttributeInjectionPassTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/AssemblyAttributeInjectionPassTest.cs @@ -207,8 +207,7 @@ public void Execute_AddsRazorViewAttribute_ToViews() node => { var csharpCode = Assert.IsType(node); - var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); Assert.Equal(expectedAttribute, token.Content); }, node => Assert.Same(@namespace, node)); @@ -255,8 +254,7 @@ public void Execute_EscapesViewPathWhenAddingAttributeToViews() node => { var csharpCode = Assert.IsType(node); - var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); Assert.Equal(expectedAttribute, token.Content); }, node => Assert.Same(@namespace, node)); @@ -311,8 +309,7 @@ public void Execute_AddsRazorPagettribute_ToPage() node => { var csharpCode = Assert.IsType(node); - var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); Assert.Equal(expectedAttribute, token.Content); }, node => Assert.Same(@namespace, node)); @@ -358,8 +355,7 @@ public void Execute_EscapesViewPathAndRouteWhenAddingAttributeToPage() node => { var csharpCode = Assert.IsType(node); - var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); Assert.Equal(expectedAttribute, token.Content); }, node => Assert.Same(@namespace, node)); diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/ModelExpressionPassTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/ModelExpressionPassTest.cs index 08869691cab..ee8943643ff 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/ModelExpressionPassTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/ModelExpressionPassTest.cs @@ -50,8 +50,7 @@ public void ModelExpressionPass_NonModelExpressionProperty_Ignored() var tagHelperNode = documentNode.GetTagHelperNode(); var setProperty = tagHelperNode.Children.OfType().Single(); - var token = Assert.IsAssignableFrom(Assert.Single(setProperty.Children)); - Assert.True(token.IsCSharp); + var token = Assert.IsAssignableFrom(Assert.Single(setProperty.Children)); Assert.Equal("17", token.Content); } @@ -85,8 +84,7 @@ public void ModelExpressionPass_ModelExpressionProperty_SimpleExpression() var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => __model.Bar)", expression.GetCSharpContent()); - var originalNode = Assert.IsAssignableFrom(expression.Children[2]); - Assert.Equal(TokenKind.CSharp, originalNode.Kind); + var originalNode = Assert.IsAssignableFrom(expression.Children[2]); Assert.Equal("Bar", originalNode.Content); var source = Assert.NotNull(originalNode.Source); Assert.Equal(new SourceSpan("test.cshtml", 51, 1, 8, 3), source); @@ -122,8 +120,7 @@ public void ModelExpressionPass_ModelExpressionProperty_ComplexExpression() var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => Bar)", expression.GetCSharpContent()); - var originalNode = Assert.IsAssignableFrom(expression.Children[1]); - Assert.Equal(TokenKind.CSharp, originalNode.Kind); + var originalNode = Assert.IsAssignableFrom(expression.Children[1]); Assert.Equal("Bar", originalNode.Content); var source = Assert.NotNull(originalNode.Source); Assert.Equal(new SourceSpan("test.cshtml", 52, 1, 9, 3), source); diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/ModelExpressionPassTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/ModelExpressionPassTest.cs index fa541193908..eaaa870678a 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/ModelExpressionPassTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/ModelExpressionPassTest.cs @@ -50,8 +50,7 @@ public void ModelExpressionPass_NonModelExpressionProperty_Ignored() var tagHelperNode = documentNode.GetTagHelperNode(); var setProperty = tagHelperNode.Children.OfType().Single(); - var token = Assert.IsAssignableFrom(Assert.Single(setProperty.Children)); - Assert.True(token.IsCSharp); + var token = Assert.IsAssignableFrom(Assert.Single(setProperty.Children)); Assert.Equal("17", token.Content); } @@ -85,8 +84,7 @@ public void ModelExpressionPass_ModelExpressionProperty_SimpleExpression() var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => __model.Bar)", expression.GetCSharpContent()); - var originalNode = Assert.IsAssignableFrom(expression.Children[2]); - Assert.Equal(TokenKind.CSharp, originalNode.Kind); + var originalNode = Assert.IsAssignableFrom(expression.Children[2]); Assert.Equal("Bar", originalNode.Content); var source = Assert.NotNull(originalNode.Source); Assert.Equal(new SourceSpan("test.cshtml", 51, 1, 8, 3), source); @@ -122,8 +120,7 @@ public void ModelExpressionPass_ModelExpressionProperty_ComplexExpression() var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => Bar)", expression.GetCSharpContent()); - var originalNode = Assert.IsAssignableFrom(expression.Children[1]); - Assert.Equal(TokenKind.CSharp, originalNode.Kind); + var originalNode = Assert.IsAssignableFrom(expression.Children[1]); Assert.Equal("Bar", originalNode.Content); var source = Assert.NotNull(originalNode.Source); Assert.Equal(new SourceSpan("test.cshtml", 52, 1, 9, 3), source); diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/ComponentMarkupEncodingPassTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/ComponentMarkupEncodingPassTest.cs index 1161ab984c2..7aafc14cfe3 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/ComponentMarkupEncodingPassTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/ComponentMarkupEncodingPassTest.cs @@ -212,11 +212,13 @@ private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument) private static string GetHtmlContent(HtmlContentIntermediateNode node) { var builder = new StringBuilder(); - var htmlTokens = node.Children.OfType().Where(t => t.IsHtml); + var htmlTokens = node.Children.OfType(); + foreach (var htmlToken in htmlTokens) { builder.Append(htmlToken.Content); } + return builder.ToString(); } } diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/NodeAssert.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/NodeAssert.cs index b6b40e08507..c9045de30b9 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/NodeAssert.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Components/NodeAssert.cs @@ -22,8 +22,7 @@ public static HtmlAttributeIntermediateNode Attribute(IntermediateNode node, str var actual = new StringBuilder(); for (var i = 0; i < attributeValueNode.Children.Count; i++) { - var token = Assert.IsAssignableFrom(attributeValueNode.Children[i]); - Assert.Equal(TokenKind.Html, token.Kind); + var token = Assert.IsAssignableFrom(attributeValueNode.Children[i]); actual.Append(token.Content); } @@ -47,8 +46,7 @@ public static HtmlContentIntermediateNode Content(IntermediateNode node, string var actual = new StringBuilder(); for (var i = 0; i < contentNode.Children.Count; i++) { - var token = Assert.IsAssignableFrom(contentNode.Children[i]); - Assert.Equal(TokenKind.Html, token.Kind); + var token = Assert.IsAssignableFrom(contentNode.Children[i]); actual.Append(token.Content); } @@ -73,8 +71,7 @@ public static HtmlAttributeIntermediateNode CSharpAttribute(IntermediateNode nod var actual = new StringBuilder(); for (var i = 0; i < attributeValueNode.Children.Count; i++) { - var token = Assert.IsAssignableFrom(attributeValueNode.Children[i]); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(attributeValueNode.Children[i]); actual.Append(token.Content); } @@ -111,8 +108,7 @@ public static HtmlContentIntermediateNode Whitespace(IntermediateNode node) var contentNode = Assert.IsType(node); for (var i = 0; i < contentNode.Children.Count; i++) { - var token = Assert.IsAssignableFrom(contentNode.Children[i]); - Assert.Equal(TokenKind.Html, token.Kind); + var token = Assert.IsAssignableFrom(contentNode.Children[i]); Assert.True(string.IsNullOrWhiteSpace(token.Content)); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs index 57f777100d7..ffe58f5bacf 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.CodeAnalysis; using static Microsoft.AspNetCore.Razor.Language.CommonMetadata; namespace Microsoft.CodeAnalysis.Razor; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DesignTimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DesignTimeNodeWriter.cs index 87ca77e6600..76fa17e0cf0 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DesignTimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DesignTimeNodeWriter.cs @@ -58,9 +58,9 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE context.CodeWriter.WritePadding(offset, node.Source, context); context.CodeWriter.WriteStartAssignment(DesignTimeDirectivePass.DesignTimeVariable); - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { context.AddSourceMappingFor(token); context.CodeWriter.Write(token.Content); @@ -68,7 +68,7 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE else { // There may be something else inside the expression like a Template or another extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } @@ -78,18 +78,20 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE else { context.CodeWriter.WriteStartAssignment(DesignTimeDirectivePass.DesignTimeVariable); - for (var i = 0; i < node.Children.Count; i++) + + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { context.CodeWriter.Write(token.Content); } else { // There may be something else inside the expression like a Template or another extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } + context.CodeWriter.WriteLine(";"); } } @@ -104,9 +106,9 @@ public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeInt context.CodeWriter.WritePadding(0, node.Source.Value, context); } - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { context.AddSourceMappingFor(token); context.CodeWriter.Write(token.Content); @@ -114,7 +116,7 @@ public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeInt else { // There may be something else inside the statement like an extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } @@ -164,9 +166,9 @@ public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext co context.CodeWriter.WritePadding(offset, firstChild.Source, context); context.CodeWriter.WriteStartAssignment(DesignTimeDirectivePass.DesignTimeVariable); - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { context.AddSourceMappingFor(token); context.CodeWriter.Write(token.Content); @@ -174,7 +176,7 @@ public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext co else { // There may be something else inside the expression like a Template or another extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } @@ -186,7 +188,7 @@ public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext co context.CodeWriter.WriteStartAssignment(DesignTimeDirectivePass.DesignTimeVariable); for (var i = 0; i < node.Children.Count; i++) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (node.Children[i] is CSharpIntermediateToken token) { if (token.Source != null) { @@ -207,9 +209,9 @@ public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext co public override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node) { - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { IDisposable linePragmaScope = null; var isWhitespaceStatement = string.IsNullOrWhiteSpace(token.Content); @@ -244,7 +246,7 @@ public override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, else { // There may be something else inside the statement like an extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/RuntimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/RuntimeNodeWriter.cs index d5889429e49..4d919754061 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/RuntimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/RuntimeNodeWriter.cs @@ -103,7 +103,7 @@ private static void WriteCSharpChildren(IntermediateNodeCollection children, Cod { for (var i = 0; i < children.Count; i++) { - if (children[i] is IntermediateToken token && token.IsCSharp) + if (children[i] is CSharpIntermediateToken token) { using (context.CodeWriter.BuildEnhancedLinePragma(token.Source, context)) { @@ -167,7 +167,7 @@ public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlA // Write content for (var i = 0; i < node.Children.Count; i++) { - if (node.Children[i] is IntermediateToken token && token.IsHtml) + if (node.Children[i] is HtmlIntermediateToken token) { context.CodeWriter.WriteStringLiteral(token.Content); } @@ -256,7 +256,7 @@ public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentI var length = 0; foreach (var child in node.Children) { - if (child is IntermediateToken token && token.IsHtml) + if (child is HtmlIntermediateToken token) { var htmlContent = token.Content.AsMemory(); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs index 09ccb5be37e..8d42ab32266 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Razor.Language.CodeGeneration; @@ -130,9 +131,9 @@ private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpEx context.CodeWriter.Write(")"); } - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { context.AddSourceMappingFor(token); context.CodeWriter.Write(token.Content!); @@ -140,7 +141,7 @@ private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpEx else { // There may be something else inside the expression like a Template or another extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } @@ -150,39 +151,31 @@ private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpEx else { context.CodeWriter.WriteStartAssignment(DesignTimeVariable); - for (var i = 0; i < node.Children.Count; i++) + + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { context.CodeWriter.Write(token.Content!); } else { // There may be something else inside the expression like a Template or another extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } + context.CodeWriter.WriteLine(";"); } } public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - var isWhitespaceStatement = true; - for (var i = 0; i < node.Children.Count; i++) + + foreach (var child in node.Children) { - var token = node.Children[i] as IntermediateToken; - if (token == null || !string.IsNullOrWhiteSpace(token.Content)) + if (child is not IntermediateToken token || !string.IsNullOrWhiteSpace(token.Content)) { isWhitespaceStatement = false; break; @@ -205,9 +198,9 @@ public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeInt return; } - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { context.AddSourceMappingFor(token); context.CodeWriter.Write(token.Content!); @@ -215,7 +208,7 @@ public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeInt else { // There may be something else inside the statement like an extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } @@ -284,18 +277,20 @@ public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext co } context.CodeWriter.WriteStartAssignment(DesignTimeVariable); - for (var i = 0; i < node.Children.Count; i++) + + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { WriteCSharpToken(context, token); } else { // There may be something else inside the expression like a Template or another extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } + context.CodeWriter.WriteLine(";"); } @@ -335,25 +330,11 @@ protected override void BeginWriteAttribute(CodeRenderingContext context, string protected override void BeginWriteAttribute(CodeRenderingContext context, IntermediateNode expression) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } - context.CodeWriter.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{ComponentsApi.RenderTreeBuilder.AddAttribute}"); context.CodeWriter.Write("-1"); context.CodeWriter.WriteParameterSeparator(); - var tokens = GetCSharpTokens(expression); - for (var i = 0; i < tokens.Count; i++) - { - context.CodeWriter.Write(tokens[i].Content!); - } + WriteCSharpTokens(context, GetCSharpTokens(expression)); } public override void WriteComponent(CodeRenderingContext context, ComponentIntermediateNode node) @@ -801,10 +782,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon } context.CodeWriter.WriteLine(); - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, tokens); if (canTypeCheck) { @@ -848,6 +826,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon { TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, argument); } + context.CodeWriter.Write(">"); } @@ -857,10 +836,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon context.CodeWriter.WriteLine(); - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, tokens); context.CodeWriter.Write(")"); @@ -883,10 +859,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon context.CodeWriter.Write("("); } - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, tokens); if (canTypeCheck && NeedsTypeCheck(node)) { @@ -925,10 +898,10 @@ static bool NeedsTypeCheck(ComponentAttributeIntermediateNode n) } } - private IReadOnlyList GetCSharpTokens(IntermediateNode node) + private static ImmutableArray GetCSharpTokens(IntermediateNode node) { // We generally expect all children to be CSharp, this is here just in case. - return node.FindDescendantNodes().Where(t => t.IsCSharp).ToArray(); + return node.FindDescendantNodes(); } public override void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node) @@ -997,20 +970,10 @@ public override void WriteComponentTypeArgument(CodeRenderingContext context, Co context.CodeWriter.Write(" = "); context.CodeWriter.Write("typeof("); - var tokens = GetCSharpTokens(node); - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, GetCSharpTokens(node)); context.CodeWriter.Write(");"); context.CodeWriter.WriteLine(); - - IReadOnlyList GetCSharpTokens(ComponentTypeArgumentIntermediateNode arg) - { - // We generally expect all children to be CSharp, this is here just in case. - return arg.FindDescendantNodes().Where(t => t.IsCSharp).ToArray(); - } } public override void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node) @@ -1095,8 +1058,6 @@ public override void WriteSplat(CodeRenderingContext context, SplatIntermediateN private void WriteSplatInnards(CodeRenderingContext context, SplatIntermediateNode node, bool canTypeCheck) { - var tokens = GetCSharpTokens(node); - if (canTypeCheck) { context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck); @@ -1106,10 +1067,7 @@ private void WriteSplatInnards(CodeRenderingContext context, SplatIntermediateNo context.CodeWriter.Write("("); } - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, GetCSharpTokens(node)); if (canTypeCheck) { @@ -1124,15 +1082,14 @@ public sealed override void WriteFormName(CodeRenderingContext context, FormName Debug.Assert(node.HasDiagnostics, "We should have reported an error for mixed content."); } - foreach (var token in node.FindDescendantNodes()) + foreach (var token in GetCSharpTokens(node)) { - if (token.IsCSharp) - { - context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck); - context.CodeWriter.Write("("); - WriteCSharpToken(context, token); - context.CodeWriter.WriteLine(");"); - } + context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck); + context.CodeWriter.Write("("); + + WriteCSharpToken(context, token); + + context.CodeWriter.WriteLine(");"); } } @@ -1219,7 +1176,15 @@ public override void WriteRenderMode(CodeRenderingContext context, RenderModeInt }); } - private void WriteCSharpToken(CodeRenderingContext context, IntermediateToken token) + private static void WriteCSharpTokens(CodeRenderingContext context, ImmutableArray tokens) + { + foreach (var token in tokens) + { + WriteCSharpToken(context, token); + } + } + + private static void WriteCSharpToken(CodeRenderingContext context, CSharpIntermediateToken token) { if (string.IsNullOrWhiteSpace(token.Content)) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentGenericTypePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentGenericTypePass.cs index f921bbb6d84..449dcf178d4 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentGenericTypePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentGenericTypePass.cs @@ -284,9 +284,9 @@ private bool TryFindGenericTypeNames(BoundAttributeDescriptor? boundAttribute, s return true; } - private string GetContent(ComponentAttributeIntermediateNode node) + private static string GetContent(ComponentAttributeIntermediateNode node) { - return string.Join(string.Empty, node.FindDescendantNodes().Where(t => t.IsCSharp).Select(t => t.Content)); + return string.Join(string.Empty, node.FindDescendantNodes().Select(t => t.Content)); } private static bool ValidateTypeArguments(ComponentIntermediateNode node, Dictionary bindings) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupBlockPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupBlockPass.cs index 48b0d56d830..44897f2a52d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupBlockPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupBlockPass.cs @@ -255,7 +255,7 @@ public override void VisitToken(IntermediateToken node) _foundNonHtml = true; } - if (node.IsCSharp) + if (node is CSharpIntermediateToken) { _foundNonHtml = true; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs index 5c87842465a..84cb08fd29c 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMarkupEncodingPass.cs @@ -88,7 +88,7 @@ public override void VisitHtml(HtmlContentIntermediateNode node) for (var i = 0; i < node.Children.Count; i++) { var child = node.Children[i]; - if (!(child is IntermediateToken token) || !token.IsHtml || string.IsNullOrEmpty(token.Content)) + if (child is not HtmlIntermediateToken token || string.IsNullOrEmpty(token.Content)) { // We only care about Html tokens. continue; @@ -112,7 +112,7 @@ public override void VisitHtml(HtmlContentIntermediateNode node) for (var i = 0; i < node.Children.Count; i++) { var child = node.Children[i]; - if (!(child is IntermediateToken token) || !token.IsHtml || string.IsNullOrEmpty(token.Content)) + if (child is not HtmlIntermediateToken token || string.IsNullOrEmpty(token.Content)) { // We only care about Html tokens. continue; @@ -134,7 +134,7 @@ public override void VisitHtml(HtmlContentIntermediateNode node) for (var i = 0; i < node.Children.Count; i++) { var child = node.Children[i]; - if (!(child is IntermediateToken token) || !token.IsHtml || string.IsNullOrEmpty(token.Content)) + if (child is not IntermediateToken token || string.IsNullOrEmpty(token.Content)) { // We only care about Html tokens. continue; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs index 9360993db40..7f8eb376481 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components; /// internal class ComponentRuntimeNodeWriter : ComponentNodeWriter { - private readonly List _currentAttributeValues = new List(); + private readonly ImmutableArray.Builder _currentAttributeValues = ImmutableArray.CreateBuilder(); private readonly ScopeStack _scopeStack = new ScopeStack(); private int _sourceSequence; @@ -28,21 +29,10 @@ public ComponentRuntimeNodeWriter(RazorLanguageVersion version) : base(version) public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - var isWhitespaceStatement = true; - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - var token = node.Children[i] as IntermediateToken; - if (token == null || !string.IsNullOrWhiteSpace(token.Content)) + if (child is not IntermediateToken token || !string.IsNullOrWhiteSpace(token.Content)) { isWhitespaceStatement = false; break; @@ -56,16 +46,16 @@ public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeInt return; } - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - if (node.Children[i] is IntermediateToken token && token.IsCSharp) + if (child is CSharpIntermediateToken token) { WriteCSharpToken(context, token); } else { // There may be something else inside the statement like an extension node. - context.RenderNode(node.Children[i]); + context.RenderNode(child); } } @@ -96,7 +86,7 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE // is another method so a sequence point can be emitted. Unfortunately any trailing C# is not mapped, although in many cases it's uninteresting // such as closing parenthesis. // - Error cases: there are no nodes, so we do nothing - var firstCSharpChild = node.Children.FirstOrDefault(IsCSharpToken) as IntermediateToken; + var firstCSharpChild = node.Children.OfType().FirstOrDefault(); using (context.CodeWriter.BuildEnhancedLinePragma(firstCSharpChild?.Source, context, characterOffset: methodInvocation.Length + 2)) { context.CodeWriter @@ -111,15 +101,17 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE // render the remaining children. We still emit the #line pragmas for the remaining csharp tokens but // these wont actually generate any sequence points for debugging. + foreach (var child in node.Children) { if (child == firstCSharpChild) { continue; } - else if (IsCSharpToken(child)) + + if (child is CSharpIntermediateToken csharpToken) { - WriteCSharpToken(context, (IntermediateToken)child); + WriteCSharpToken(context, csharpToken); } else { @@ -129,22 +121,10 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE } context.CodeWriter.WriteEndMethodInvocation(); - - static bool IsCSharpToken(IntermediateNode n) => n is IntermediateToken token && token.IsCSharp; } public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - // In cases like "somestring @variable", Razor tokenizes it as: // [0] HtmlContent="somestring" // [1] CsharpContent="variable" Prefix=" " @@ -154,9 +134,9 @@ public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext co _currentAttributeValues.Add(IntermediateNodeFactory.HtmlToken(node.Prefix)); } - for (var i = 0; i < node.Children.Count; i++) + foreach (var child in node.Children) { - _currentAttributeValues.Add((IntermediateToken)node.Children[i]); + _currentAttributeValues.Add((IntermediateToken)child); } } @@ -261,28 +241,17 @@ public override void WriteMarkupElement(CodeRenderingContext context, MarkupElem public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (node == null) - { - throw new ArgumentNullException(nameof(node)); - } - Debug.Assert(_currentAttributeValues.Count == 0); context.RenderChildren(node); if (node.AttributeNameExpression == null) { - WriteAttribute(context, node.AttributeName, _currentAttributeValues); + WriteAttribute(context, node.AttributeName, _currentAttributeValues.ToImmutableAndClear()); } else { - WriteAttribute(context, node.AttributeNameExpression, _currentAttributeValues); + WriteAttribute(context, node.AttributeNameExpression, _currentAttributeValues.ToImmutableAndClear()); } - _currentAttributeValues.Clear(); if (!string.IsNullOrEmpty(node.EventUpdatesAttributeName)) { @@ -713,10 +682,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon context.CodeWriter.Write("("); } - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, tokens); if (canTypeCheck) { @@ -762,10 +728,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon context.CodeWriter.Write("this"); context.CodeWriter.Write(", "); - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, tokens); context.CodeWriter.Write(")"); @@ -785,10 +748,7 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon context.CodeWriter.Write("("); } - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, tokens); if (canTypeCheck && NeedsTypeCheck(node)) { @@ -821,22 +781,22 @@ static void QualifyEventCallback(CodeWriter codeWriter, string typeName, bool? e } } - IReadOnlyList GetHtmlTokens(HtmlContentIntermediateNode html) - { - // We generally expect all children to be HTML, this is here just in case. - return html.FindDescendantNodes().Where(t => t.IsHtml).ToArray(); - } - static bool NeedsTypeCheck(ComponentAttributeIntermediateNode n) { return n.BoundAttribute != null && !n.BoundAttribute.IsWeaklyTyped(); } } - private IReadOnlyList GetCSharpTokens(IntermediateNode node) + private static ImmutableArray GetHtmlTokens(IntermediateNode node) + { + // We generally expect all children to be HTML, this is here just in case. + return node.FindDescendantNodes(); + } + + private static ImmutableArray GetCSharpTokens(IntermediateNode node) { // We generally expect all children to be CSharp, this is here just in case. - return node.FindDescendantNodes().Where(t => t.IsCSharp).ToArray(); + return node.FindDescendantNodes(); } public override void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node) @@ -963,8 +923,6 @@ public override void WriteSplat(CodeRenderingContext context, SplatIntermediateN private void WriteSplatInnards(CodeRenderingContext context, SplatIntermediateNode node, bool canTypeCheck) { - var tokens = GetCSharpTokens(node); - if (canTypeCheck) { context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck); @@ -974,10 +932,7 @@ private void WriteSplatInnards(CodeRenderingContext context, SplatIntermediateNo context.CodeWriter.Write("("); } - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, tokens[i]); - } + WriteCSharpTokens(context, GetCSharpTokens(node)); if (canTypeCheck) { @@ -1067,11 +1022,11 @@ public override void WriteRenderMode(CodeRenderingContext context, RenderModeInt }); } - private void WriteAttribute(CodeRenderingContext context, string key, IReadOnlyList value) + private void WriteAttribute(CodeRenderingContext context, string key, ImmutableArray value) { BeginWriteAttribute(context, key); - if (value.Count > 0) + if (value.Length > 0) { context.CodeWriter.WriteParameterSeparator(); WriteAttributeValue(context, value); @@ -1087,14 +1042,16 @@ private void WriteAttribute(CodeRenderingContext context, string key, IReadOnlyL context.CodeWriter.WriteEndMethodInvocation(); } - private void WriteAttribute(CodeRenderingContext context, IntermediateNode nameExpression, IReadOnlyList value) + private void WriteAttribute(CodeRenderingContext context, IntermediateNode nameExpression, ImmutableArray value) { BeginWriteAttribute(context, nameExpression); - if (value.Count > 0) + + if (value.Length > 0) { context.CodeWriter.WriteParameterSeparator(); WriteAttributeValue(context, value); } + context.CodeWriter.WriteEndMethodInvocation(); } @@ -1114,7 +1071,7 @@ protected override void BeginWriteAttribute(CodeRenderingContext context, Interm context.CodeWriter.WriteParameterSeparator(); var tokens = GetCSharpTokens(nameExpression); - for (var i = 0; i < tokens.Count; i++) + for (var i = 0; i < tokens.Length; i++) { WriteCSharpToken(context, tokens[i]); } @@ -1124,10 +1081,14 @@ private static string GetHtmlContent(HtmlContentIntermediateNode node) { using var _ = StringBuilderPool.GetPooledObject(out var builder); - var htmlTokens = node.Children.OfType().Where(t => t.IsHtml); - foreach (var htmlToken in htmlTokens) + var htmlTokens = node.Children.OfType(); + + foreach (var child in node.Children) { - builder.Append(htmlToken.Content); + if (child is HtmlIntermediateToken htmlToken) + { + builder.Append(htmlToken.Content); + } } return builder.ToString(); @@ -1141,93 +1102,123 @@ private static string GetHtmlContent(HtmlContentIntermediateNode node) // Only the mixed case is complicated, we want to turn it into code that will concatenate // the values into a string at runtime. - private static void WriteAttributeValue(CodeRenderingContext context, IReadOnlyList tokens) + private static void WriteAttributeValue(CodeRenderingContext context, ImmutableArray tokens) { - if (tokens == null) + if (tokens.Length == 0) { - throw new ArgumentNullException(nameof(tokens)); + return; } var writer = context.CodeWriter; var hasHtml = false; var hasCSharp = false; - for (var i = 0; i < tokens.Count; i++) + + foreach (var token in tokens) { - if (tokens[i].IsCSharp) + if (token is CSharpIntermediateToken) { hasCSharp |= true; } else { + Debug.Assert(token is HtmlIntermediateToken); hasHtml |= true; } } - if (hasHtml && hasCSharp) + if (!hasCSharp && !hasHtml) { - // If it's a C# expression, we have to wrap it in parens, otherwise things like ternary - // expressions don't compose with concatenation. However, this is a little complicated - // because C# tokens themselves aren't guaranteed to be distinct expressions. We want - // to treat all contiguous C# tokens as a single expression. - var insideCSharp = false; - for (var i = 0; i < tokens.Count; i++) + Assumed.Unreachable("Found attribute whose value is neither HTML nor CSharp"); + } + + // If we only have C# tokens, we write them out directly. + if (hasCSharp && !hasHtml) + { + foreach (var token in tokens) { - var token = tokens[i]; - if (token.IsCSharp) - { - if (!insideCSharp) - { - if (i != 0) - { - writer.Write(" + "); - } + WriteCSharpToken(context, (CSharpIntermediateToken)token); + } - writer.Write("("); - insideCSharp = true; - } + return; + } - WriteCSharpToken(context, token); - } - else - { - if (insideCSharp) - { - writer.Write(")"); - insideCSharp = false; - } + // If we only have HTML tokens, we write out a single string literal. + if (hasHtml && !hasCSharp) + { + using var _ = StringBuilderPool.GetPooledObject(out var builder); + + foreach (var token in tokens) + { + Debug.Assert(token is HtmlIntermediateToken); + builder.Append(token.Content); + } - if (i != 0) + writer.WriteStringLiteral(builder.ToString()); + return; + } + + // If it's a C# expression, we have to wrap it in parenthesize, otherwise things like ternary + // expressions don't compose with concatenation. However, this is a little complicated + // because C# tokens themselves aren't guaranteed to be distinct expressions. We want + // to treat all contiguous C# tokens as a single expression. + var insideCSharp = false; + var first = true; + foreach (var token in tokens) + { + if (token is CSharpIntermediateToken csharpToken) + { + if (!insideCSharp) + { + // Transition to a new C# expression + if (!first) { writer.Write(" + "); } - writer.WriteStringLiteral(token.Content); + writer.Write("("); + insideCSharp = true; } - } - if (insideCSharp) + WriteCSharpToken(context, csharpToken); + } + else { - writer.Write(")"); + if (insideCSharp) + { + // Transition to HTML, close out the C# expression + writer.Write(")"); + insideCSharp = false; + } + + if (!first) + { + writer.Write(" + "); + } + + writer.WriteStringLiteral(token.Content); } - } - else if (hasCSharp) - { - foreach (var token in tokens) + + if (first) { - WriteCSharpToken(context, token); + first = false; } } - else if (hasHtml) + + if (insideCSharp) { - writer.WriteStringLiteral(string.Join("", tokens.Select(t => t.Content))); + writer.Write(")"); } - else + } + + private static void WriteCSharpTokens(CodeRenderingContext context, ImmutableArray tokens) + { + foreach (var token in tokens) { - throw new InvalidOperationException("Found attribute whose value is neither HTML nor CSharp"); + WriteCSharpToken(context, token); } } - private static void WriteCSharpToken(CodeRenderingContext context, IntermediateToken token) + private static void WriteCSharpToken(CodeRenderingContext context, CSharpIntermediateToken token) { if (token.Source?.FilePath == null) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentSplatLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentSplatLoweringPass.cs index 201e44e82ca..1d41a48aa3b 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentSplatLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentSplatLoweringPass.cs @@ -35,7 +35,7 @@ private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperDirectiv Source = node.Source, }; - result.Children.AddRange(node.FindDescendantNodes().Where(t => t.IsCSharp)); + result.Children.AddRange(node.FindDescendantNodes()); result.AddDiagnosticsFromNode(node); return result; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DefaultTagHelperTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DefaultTagHelperTargetExtension.cs index 2df263902ae..fb990eae264 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DefaultTagHelperTargetExtension.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DefaultTagHelperTargetExtension.cs @@ -382,9 +382,7 @@ public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelpe var accessor = GetPropertyAccessor(node); var assignmentPrefixLength = accessor.Length + " = ".Length; if (node.BoundAttribute.IsEnum && - node.Children.Count == 1 && - node.Children.First() is IntermediateToken token && - token.IsCSharp) + node.Children is [CSharpIntermediateToken token]) { assignmentPrefixLength += $"global::{node.BoundAttribute.TypeName}.".Length; @@ -429,9 +427,7 @@ public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelpe context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node)); if (node.BoundAttribute.IsEnum && - node.Children.Count == 1 && - node.Children.First() is IntermediateToken token && - token.IsCSharp) + node.Children is [CSharpIntermediateToken token]) { context.CodeWriter .Write("global::") @@ -634,7 +630,7 @@ private string GetContent(HtmlContentIntermediateNode node) var builder = new StringBuilder(); for (var i = 0; i < node.Children.Count; i++) { - if (node.Children[i] is IntermediateToken token && token.IsHtml) + if (node.Children[i] is HtmlIntermediateToken token) { builder.Append(token.Content); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs index 574f795e034..893fddcec14 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs @@ -155,7 +155,7 @@ private string GetContent(HtmlContentIntermediateNode node) var builder = new StringBuilder(); for (var i = 0; i < node.Children.Count; i++) { - if (node.Children[i] is IntermediateToken token && token.IsHtml) + if (node.Children[i] is HtmlIntermediateToken token) { builder.Append(token.Content); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs index adca9647a76..59d611c5ee6 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/ViewCssScopePass.cs @@ -39,7 +39,7 @@ private void ProcessElement(HtmlContentIntermediateNode node, string cssScope, r for (var i = 0; i < node.Children.Count; i++) { var child = node.Children[i]; - if (child is IntermediateToken token && token.IsHtml) + if (child is HtmlIntermediateToken token) { if (IsValidElement(token, previousToken)) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs index e8310b62c8d..98e06ac09f8 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs @@ -6,12 +6,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public sealed class CSharpIntermediateToken : IntermediateToken { public CSharpIntermediateToken(string? content, SourceSpan? source) - : base(TokenKind.CSharp, content, source) + : base(content, source) { } internal CSharpIntermediateToken(LazyContent content, SourceSpan? source) - : base(TokenKind.CSharp, content, source) + : base(content, source) { } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/ComponentTypeArgumentIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/ComponentTypeArgumentIntermediateNode.cs index 5c385729266..8b6160e3d11 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/ComponentTypeArgumentIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/ComponentTypeArgumentIntermediateNode.cs @@ -24,9 +24,9 @@ public ComponentTypeArgumentIntermediateNode(TagHelperPropertyIntermediateNode p Debug.Assert(propertyNode.Children.Count == 1); Value = propertyNode.Children[0] switch { - IntermediateToken t => t, - CSharpExpressionIntermediateNode c => (IntermediateToken)c.Children[0], // TODO: can we break this in error cases? - _ => Assumed.Unreachable() + CSharpIntermediateToken t => t, + CSharpExpressionIntermediateNode c => (CSharpIntermediateToken)c.Children[0], // TODO: can we break this in error cases? + _ => Assumed.Unreachable() }; Children = [Value]; @@ -41,7 +41,7 @@ public ComponentTypeArgumentIntermediateNode(TagHelperPropertyIntermediateNode p public TagHelperDescriptor TagHelper { get; set; } - public IntermediateToken Value { get; set; } + public CSharpIntermediateToken Value { get; set; } public override void Accept(IntermediateNodeVisitor visitor) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs index 39eda3b6aea..c192d8e10aa 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs @@ -6,12 +6,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public sealed class HtmlIntermediateToken : IntermediateToken { public HtmlIntermediateToken(string? content, SourceSpan? source) - : base(TokenKind.Html, content, source) + : base(content, source) { } internal HtmlIntermediateToken(LazyContent content, SourceSpan? source) - : base(TokenKind.Html, content, source) + : base(content, source) { } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs index f0b6c62f05f..93f4ce841f5 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs @@ -5,11 +5,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public abstract class IntermediateToken : IntermediateNode { - public TokenKind Kind { get; } - - public bool IsCSharp => Kind == TokenKind.CSharp; - public bool IsHtml => Kind == TokenKind.Html; - public bool IsLazy { get; } private object? _content; @@ -19,9 +14,8 @@ public string? Content public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; - protected IntermediateToken(TokenKind kind, string? content, SourceSpan? source) + protected IntermediateToken(string? content, SourceSpan? source) { - Kind = kind; _content = content; IsLazy = false; @@ -31,9 +25,8 @@ protected IntermediateToken(TokenKind kind, string? content, SourceSpan? source) } } - private protected IntermediateToken(TokenKind kind, LazyContent content, SourceSpan? source) + private protected IntermediateToken(LazyContent content, SourceSpan? source) { - Kind = kind; _content = content; IsLazy = true; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/TokenKind.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/TokenKind.cs deleted file mode 100644 index 5c4b8e81a65..00000000000 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/TokenKind.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -namespace Microsoft.AspNetCore.Razor.Language.Intermediate; - -public enum TokenKind -{ - Unknown, - CSharp, - Html, -} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs index 68642581bc6..bf1e94ed080 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ModelExpressionPass.cs @@ -39,7 +39,7 @@ public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode no expression.Children.Add(IntermediateNodeFactory.CSharpToken("ModelExpressionProvider.CreateModelExpression(ViewData, __model => ")); - if (node.Children.Count == 1 && node.Children[0] is IntermediateToken token && token.IsCSharp) + if (node.Children.Count == 1 && node.Children[0] is CSharpIntermediateToken token) { // A 'simple' expression will look like __model => __model.Foo @@ -55,8 +55,7 @@ public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode no { for (var j = 0; j < nestedExpression.Children.Count; j++) { - if (nestedExpression.Children[j] is IntermediateToken csharpToken && - csharpToken.IsCSharp) + if (nestedExpression.Children[j] is CSharpIntermediateToken csharpToken) { expression.Children.Add(csharpToken); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs index f59dfea40cc..b8d1cb035db 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs @@ -67,7 +67,14 @@ public override void VisitCSharpCodeAttributeValue(CSharpCodeAttributeValueInter public override void VisitToken(IntermediateToken node) { - WriteContentNode(node, node.Kind.ToString(), node.Content); + var kind = node switch + { + CSharpIntermediateToken => "CSharp", + HtmlIntermediateToken => "Html", + _ => "Unknown" + }; + + WriteContentNode(node, kind, node.Content); } public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeAssert.cs b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeAssert.cs index e50a97f7998..dc86dbf219b 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeAssert.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeAssert.cs @@ -72,8 +72,7 @@ public static void Html(string expected, IntermediateNode node) using var _ = StringBuilderPool.GetPooledObject(out var content); for (var i = 0; i < html.Children.Count; i++) { - var token = Assert.IsAssignableFrom(html.Children[i]); - Assert.Equal(TokenKind.Html, token.Kind); + var token = Assert.IsAssignableFrom(html.Children[i]); content.Append(token.Content); } @@ -93,8 +92,7 @@ public static void CSharpCode(string expected, IntermediateNode node) var content = new StringBuilder(); for (var i = 0; i < statement.Children.Count; i++) { - var token = Assert.IsAssignableFrom(statement.Children[i]); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(statement.Children[i]); content.Append(token.Content); } @@ -180,8 +178,7 @@ public static void CSharpExpressionAttributeValue(string prefix, string expected using var _ = StringBuilderPool.GetPooledObject(out var content); for (var i = 0; i < attributeValue.Children.Count; i++) { - var token = Assert.IsAssignableFrom(attributeValue.Children[i]); - Assert.True(token.IsCSharp); + var token = Assert.IsAssignableFrom(attributeValue.Children[i]); content.Append(token.Content); } @@ -203,8 +200,7 @@ public static void LiteralAttributeValue(string prefix, string expected, Interme using var _ = StringBuilderPool.GetPooledObject(out var content); for (var i = 0; i < attributeValue.Children.Count; i++) { - var token = Assert.IsAssignableFrom(attributeValue.Children[i]); - Assert.True(token.IsHtml); + var token = Assert.IsAssignableFrom(attributeValue.Children[i]); content.Append(token.Content); } @@ -226,8 +222,7 @@ public static void CSharpExpression(string expected, IntermediateNode node) using var _ = StringBuilderPool.GetPooledObject(out var content); for (var i = 0; i < cSharp.Children.Count; i++) { - var token = Assert.IsAssignableFrom(cSharp.Children[i]); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(cSharp.Children[i]); content.Append(token.Content); } @@ -247,8 +242,7 @@ public static void BeginInstrumentation(string expected, IntermediateNode node) using var _ = StringBuilderPool.GetPooledObject(out var content); for (var i = 0; i < beginNode.Children.Count; i++) { - var token = Assert.IsAssignableFrom(beginNode.Children[i]); - Assert.True(token.IsCSharp); + var token = Assert.IsAssignableFrom(beginNode.Children[i]); content.Append(token.Content); } @@ -268,8 +262,7 @@ public static void EndInstrumentation(IntermediateNode node) using var _ = StringBuilderPool.GetPooledObject(out var content); for (var i = 0; i < endNode.Children.Count; i++) { - var token = Assert.IsAssignableFrom(endNode.Children[i]); - Assert.Equal(TokenKind.CSharp, token.Kind); + var token = Assert.IsAssignableFrom(endNode.Children[i]); content.Append(token.Content); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeExtensions.cs index 8a5095d85db..7d06c68e8c6 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/Intermediate/IntermediateNodeExtensions.cs @@ -15,7 +15,7 @@ public static string GetCSharpContent(this IntermediateNode node) foreach (var child in node.Children) { - if (child is IntermediateToken { Kind: TokenKind.CSharp } csharpToken) + if (child is CSharpIntermediateToken csharpToken) { builder.Append(csharpToken.Content); } From 0b7a4397e7c2276ccea28a80c13eb1ac094a7d84 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 7 Jul 2025 13:36:43 -0700 Subject: [PATCH 08/10] Make IntermediateToken.Content non-nullable --- .../src/CSharp/GenericTypeNameRewriter.cs | 3 ++- .../Components/ComponentDesignTimeNodeWriter.cs | 6 +++--- .../ComponentEventHandlerLoweringPass.cs | 2 +- .../Components/ComponentRuntimeNodeWriter.cs | 6 +++--- .../Intermediate/CSharpIntermediateToken.cs | 2 +- .../Language/Intermediate/HtmlIntermediateToken.cs | 2 +- .../Intermediate/IntermediateNodeFormatter.cs | 2 +- .../src/Language/Intermediate/IntermediateToken.cs | 8 ++++---- .../src/Language/Intermediate/LazyContent.cs | 14 +++++++------- 9 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/GenericTypeNameRewriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/GenericTypeNameRewriter.cs index c27af271fed..82f8fa0cdcf 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/GenericTypeNameRewriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/GenericTypeNameRewriter.cs @@ -66,7 +66,8 @@ public override SyntaxNode Visit(SyntaxNode node) // compared to leaving the type parameter in place. // // We add our own diagnostics for missing/invalid type parameters anyway. - var replacement = binding?.Value?.Content ?? "object"; + var content = binding?.Value?.Content; + var replacement = !string.IsNullOrWhiteSpace(content) ? content : "object"; return identifier.Update(SyntaxFactory.Identifier(replacement).WithTriviaFrom(identifier.Identifier)); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs index 8d42ab32266..6baafccca58 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDesignTimeNodeWriter.cs @@ -136,7 +136,7 @@ private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpEx if (child is CSharpIntermediateToken token) { context.AddSourceMappingFor(token); - context.CodeWriter.Write(token.Content!); + context.CodeWriter.Write(token.Content); } else { @@ -156,7 +156,7 @@ private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpEx { if (child is CSharpIntermediateToken token) { - context.CodeWriter.Write(token.Content!); + context.CodeWriter.Write(token.Content); } else { @@ -203,7 +203,7 @@ public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeInt if (child is CSharpIntermediateToken token) { context.AddSourceMappingFor(token); - context.CodeWriter.Write(token.Content!); + context.CodeWriter.Write(token.Content); } else { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs index 514664291c6..28dbff3e4f4 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs @@ -245,7 +245,7 @@ private static ImmutableArray GetAttributeContent(Intermediat // an expression. var tokens = htmlContentNode.FindDescendantNodes(); - var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content!.Replace("\"", "\\\""))) + "\""; + var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content.Replace("\"", "\\\""))) + "\""; return [IntermediateNodeFactory.CSharpToken(content)]; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs index 7f8eb376481..7ea498f99a9 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs @@ -95,7 +95,7 @@ public override void WriteCSharpExpression(CodeRenderingContext context, CSharpE if (firstCSharpChild is not null) { - context.CodeWriter.Write(firstCSharpChild.Content!); + context.CodeWriter.Write(firstCSharpChild.Content); } } @@ -1222,13 +1222,13 @@ private static void WriteCSharpToken(CodeRenderingContext context, CSharpInterme { if (token.Source?.FilePath == null) { - context.CodeWriter.Write(token.Content!); + context.CodeWriter.Write(token.Content); return; } using (context.CodeWriter.BuildEnhancedLinePragma(token.Source, context)) { - context.CodeWriter.Write(token.Content!); + context.CodeWriter.Write(token.Content); } } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs index 98e06ac09f8..b9798fb20d7 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/CSharpIntermediateToken.cs @@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public sealed class CSharpIntermediateToken : IntermediateToken { - public CSharpIntermediateToken(string? content, SourceSpan? source) + public CSharpIntermediateToken(string content, SourceSpan? source) : base(content, source) { } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs index c192d8e10aa..6af9e1e8ca2 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/HtmlIntermediateToken.cs @@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public sealed class HtmlIntermediateToken : IntermediateToken { - public HtmlIntermediateToken(string? content, SourceSpan? source) + public HtmlIntermediateToken(string content, SourceSpan? source) : base(content, source) { } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs index a2ba8b62493..39d0a46c106 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFormatter.cs @@ -71,7 +71,7 @@ public void WriteChildren(IntermediateNodeCollection children) { if (child is IntermediateToken token) { - WriteEscaped(token.Content!); + WriteEscaped(token.Content); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs index 93f4ce841f5..b1c7dd83644 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateToken.cs @@ -7,14 +7,14 @@ public abstract class IntermediateToken : IntermediateNode { public bool IsLazy { get; } - private object? _content; + private object _content; - public string? Content - => _content is LazyContent lazy ? lazy.Value : (string?)_content; + public string Content + => _content is LazyContent lazy ? lazy.Value : (string)_content; public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly; - protected IntermediateToken(string? content, SourceSpan? source) + protected IntermediateToken(string content, SourceSpan? source) { _content = content; IsLazy = false; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs index f92e01abd4a..4a6ae3ac0f1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/LazyContent.cs @@ -13,16 +13,16 @@ private LazyContent() { } - public abstract string? Value { get; } + public abstract string Value { get; } - public static LazyContent Create(T arg, Func contentFactory) + public static LazyContent Create(T arg, Func contentFactory) { ArgHelper.ThrowIfNull(contentFactory); return new LazyContentImpl(arg, contentFactory); } - private sealed class LazyContentImpl(T arg, Func contentFactory) : LazyContent + private sealed class LazyContentImpl(T arg, Func contentFactory) : LazyContent { private const int Uninitialized = 0; private const int Computing = 1; @@ -31,10 +31,10 @@ private sealed class LazyContentImpl(T arg, Func contentFactory) private T _arg = arg; private Func _contentFactory = contentFactory; - private string? _value; + private string _value = null!; private int _state; - public override string? Value + public override string Value { get { @@ -45,7 +45,7 @@ public override string? Value } } - private string? GetOrComputeAndStoreValue() + private string GetOrComputeAndStoreValue() { SpinWait spinner = default; @@ -57,7 +57,7 @@ public override string? Value Debug.Assert(_contentFactory is not null, "Content factory should not be null at this point."); // This thread gets to compute the value and clear the references for GC. - _value = _contentFactory(_arg); + _value = _contentFactory(_arg) ?? string.Empty; _arg = default!; _contentFactory = null!; From faada6b79d457de7f1f7f221a949904629f2a5aa Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 7 Jul 2025 13:40:04 -0700 Subject: [PATCH 09/10] Make LazyContent token factory methods generic on argument type --- ...faultRazorIntermediateNodeLoweringPhase.cs | 48 +++++++++---------- .../Intermediate/IntermediateNodeFactory.cs | 8 ++-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs index 49621df0824..a22fe867146 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -759,8 +759,8 @@ public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValu }); _builder.Add(IntermediateNodeFactory.HtmlToken( - factoryArgument: node, - contentFactory: static node => ((MarkupLiteralAttributeValueSyntax)node).Value?.GetContent() ?? string.Empty, + arg: node, + contentFactory: static node => node.Value?.GetContent() ?? string.Empty, source: BuildSourceSpanFromNode(node.Value))); _builder.Pop(); @@ -895,8 +895,8 @@ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax } _builder.Add(IntermediateNodeFactory.CSharpToken( - factoryArgument: node, - contentFactory: static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), + arg: node, + contentFactory: static node => node.GetContent(), source: BuildSourceSpanFromNode(node))); base.VisitCSharpExpressionLiteral(node); @@ -918,8 +918,8 @@ public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax no } _builder.Add(IntermediateNodeFactory.CSharpToken( - factoryArgument: node, - contentFactory: static node => ((CSharpStatementLiteralSyntax)node).GetContent(), + arg: node, + contentFactory: static node => node.GetContent(), source: BuildSourceSpanFromNode(node))); if (!isAttributeValue) @@ -1012,8 +1012,8 @@ private void VisitHtmlContent(SyntaxNode node) _builder.Push(contentNode); _builder.Add(IntermediateNodeFactory.HtmlToken( - factoryArgument: node, - contentFactory: static node => ((SyntaxNode)node).GetContent(), + arg: node, + contentFactory: static node => node.GetContent(), source)); _builder.Pop(); @@ -1251,8 +1251,8 @@ private void VisitAttributeValue(SyntaxNode node) private void Combine(HtmlContentIntermediateNode node, SyntaxNode item) { node.Children.Add(IntermediateNodeFactory.HtmlToken( - factoryArgument: item, - contentFactory: static item => ((SyntaxNode)item).GetContent(), + arg: item, + contentFactory: static item => item.GetContent(), source: BuildSourceSpanFromNode(item))); if (node.Source is SourceSpan source) @@ -1490,8 +1490,8 @@ public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValu }); _builder.Add(IntermediateNodeFactory.HtmlToken( - factoryArgument: node, - contentFactory: static node => ((MarkupLiteralAttributeValueSyntax)node).Value?.GetContent() ?? string.Empty, + arg: node, + contentFactory: static node => node.Value?.GetContent() ?? string.Empty, source: BuildSourceSpanFromNode(node.Value))); _builder.Pop(); @@ -1509,8 +1509,8 @@ public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node) }); _builder.Add(IntermediateNodeFactory.HtmlToken( - factoryArgument: node, - contentFactory: static node => ((MarkupTextLiteralSyntax)node).GetContent() ?? string.Empty, + arg: node, + contentFactory: static node => node.GetContent() ?? string.Empty, source: BuildSourceSpanFromNode(node))); _builder.Pop(); @@ -1558,8 +1558,8 @@ public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node) Children = { IntermediateNodeFactory.HtmlToken( - factoryArgument: node, - contentFactory: static node => ((MarkupTextLiteralSyntax)node).GetContent(), + arg: node, + contentFactory: static node => node.GetContent(), source) } }); @@ -1731,8 +1731,8 @@ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax } _builder.Add(IntermediateNodeFactory.CSharpToken( - factoryArgument: node, - contentFactory: static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), + arg: node, + contentFactory: static node => node.GetContent(), source: BuildSourceSpanFromNode(node))); base.VisitCSharpExpressionLiteral(node); @@ -1754,8 +1754,8 @@ public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax no } _builder.Add(IntermediateNodeFactory.CSharpToken( - factoryArgument: node, - contentFactory: static node => ((CSharpStatementLiteralSyntax)node).GetContent(), + arg: node, + contentFactory: static node => node.GetContent(), source: BuildSourceSpanFromNode(node))); if (!isAttributeValue) @@ -2205,8 +2205,8 @@ private void VisitAttributeValue(SyntaxNode node) private void Combine(HtmlContentIntermediateNode node, SyntaxNode item) { node.Children.Add(IntermediateNodeFactory.HtmlToken( - factoryArgument: item, - contentFactory: static item => ((SyntaxNode)item).GetContent(), + arg: item, + contentFactory: static item => item.GetContent(), source: BuildSourceSpanFromNode(item))); if (node.Source is SourceSpan source) @@ -2328,8 +2328,8 @@ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax } _builder.Add(IntermediateNodeFactory.CSharpToken( - factoryArgument: node, - contentFactory: static node => ((CSharpExpressionLiteralSyntax)node).GetContent(), + arg: node, + contentFactory: static node => node.GetContent(), source: BuildSourceSpanFromNode(node))); } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs index 1ea061c19d8..14dfcb8ef25 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/IntermediateNodeFactory.cs @@ -10,12 +10,12 @@ internal static class IntermediateNodeFactory public static CSharpIntermediateToken CSharpToken(string content, SourceSpan? source = null) => new(content, source); - public static CSharpIntermediateToken CSharpToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new(LazyContent.Create(factoryArgument, contentFactory), source); + public static CSharpIntermediateToken CSharpToken(T arg, Func contentFactory, SourceSpan? source = null) + => new(LazyContent.Create(arg, contentFactory), source); public static HtmlIntermediateToken HtmlToken(string content, SourceSpan? source = null) => new(content, source); - public static HtmlIntermediateToken HtmlToken(object factoryArgument, Func contentFactory, SourceSpan? source = null) - => new(LazyContent.Create(factoryArgument, contentFactory), source); + public static HtmlIntermediateToken HtmlToken(T arg, Func contentFactory, SourceSpan? source = null) + => new(LazyContent.Create(arg, contentFactory), source); } From 94a4b38d74ff40f22f337e2d961345056c1e5f84 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 11 Aug 2025 10:49:25 -0700 Subject: [PATCH 10/10] Fix typo in comment "parenthesize" -> "parentheses" --- .../src/Language/Components/ComponentRuntimeNodeWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs index 7ea498f99a9..9124634d941 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentRuntimeNodeWriter.cs @@ -1157,7 +1157,7 @@ private static void WriteAttributeValue(CodeRenderingContext context, ImmutableA return; } - // If it's a C# expression, we have to wrap it in parenthesize, otherwise things like ternary + // If it's a C# expression, we have to wrap it in parentheses, otherwise things like ternary // expressions don't compose with concatenation. However, this is a little complicated // because C# tokens themselves aren't guaranteed to be distinct expressions. We want // to treat all contiguous C# tokens as a single expression.