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 97226a37870..c1b889cc690 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 @@ -293,7 +293,7 @@ public void Execute_EscapesViewPathWhenAddingAttributeToViews() } [Fact] - public void Execute_AddsRazorPagettribute_ToPage() + public void Execute_AddsRazorPageAttribute_ToPage() { // Arrange var source = TestRazorSourceDocument.Create("test", RazorSourceDocumentProperties.Create(filePath: null, relativePath: "/Views/Index.cshtml")); @@ -406,4 +406,211 @@ public void Execute_EscapesViewPathAndRouteWhenAddingAttributeToPage() }, node => Assert.Same(@namespace, node)); } + + [Fact] + public void Execute_AddsRazorPageAttribute_WithCustomExpression() + { + // Arrange + var source = TestRazorSourceDocument.Create("test", RazorSourceDocumentProperties.Create(filePath: null, relativePath: "/Views/Index.cshtml")); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + + var expectedAttribute = "[assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@\"/Views/Index.cshtml\", typeof(SomeNamespace.SomeName), global::SomeNamespace.SomeName.__RouteTemplate)]"; + + var documentNode = new DocumentIntermediateNode() + { + DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind, + Options = codeDocument.CodeGenerationOptions + }; + + var builder = IntermediateNodeBuilder.Create(documentNode); + + var pageDirective = new DirectiveIntermediateNode + { + Directive = PageDirective.Directive, + Children = + { + new DirectiveTokenIntermediateNode + { + Content = "AppRoutes.Home", + } + }, + }; + + builder.Add(pageDirective); + + var @namespace = new NamespaceDeclarationIntermediateNode + { + Content = "SomeNamespace", + Annotations = + { + [CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace + } + }; + + builder.Push(@namespace); + + var @class = new ClassDeclarationIntermediateNode + { + ClassName = "SomeName", + Annotations = + { + [CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass, + } + }; + + builder.Add(@class); + + // Act + ProjectEngine.ExecutePass(codeDocument, documentNode); + + // Assert + Assert.Collection(documentNode.Children, + node => Assert.Same(pageDirective, node), + node => + { + var csharpCode = Assert.IsType(node); + var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); + Assert.Equal(TokenKind.CSharp, token.Kind); + Assert.Equal(expectedAttribute, token.Content); + }, + node => Assert.Same(@namespace, node)); + } + + [Fact] + public void Execute_AddsRazorPageAttribute_WithStringConcatenationExpression() + { + // Arrange + var source = TestRazorSourceDocument.Create("test", RazorSourceDocumentProperties.Create(filePath: null, relativePath: "/Views/Index.cshtml")); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + + var expectedAttribute = "[assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@\"/Views/Index.cshtml\", typeof(SomeNamespace.SomeName), global::SomeNamespace.SomeName.__RouteTemplate)]"; + + var documentNode = new DocumentIntermediateNode() + { + DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind, + Options = codeDocument.CodeGenerationOptions + }; + + var builder = IntermediateNodeBuilder.Create(documentNode); + + var pageDirective = new DirectiveIntermediateNode + { + Directive = PageDirective.Directive, + Children = + { + new LazyIntermediateToken + { + Content = "AppRoutes.Content + AppRoutes.Content.Home", + } + }, + }; + + builder.Add(pageDirective); + + var @namespace = new NamespaceDeclarationIntermediateNode + { + Content = "SomeNamespace", + Annotations = + { + [CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace + } + }; + + builder.Push(@namespace); + + var @class = new ClassDeclarationIntermediateNode + { + ClassName = "SomeName", + Annotations = + { + [CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass, + } + }; + + builder.Add(@class); + + // Act + ProjectEngine.ExecutePass(codeDocument, documentNode); + + // Assert + Assert.Collection(documentNode.Children, + node => Assert.Same(pageDirective, node), + node => + { + var csharpCode = Assert.IsType(node); + var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); + Assert.Equal(TokenKind.CSharp, token.Kind); + Assert.Equal(expectedAttribute, token.Content); + }, + node => Assert.Same(@namespace, node)); + } + + [Fact] + public void Execute_AddsRazorPageAttribute_WithInterpolatedStringExpression() + { + // Arrange + var source = TestRazorSourceDocument.Create("test", RazorSourceDocumentProperties.Create(filePath: null, relativePath: "/Views/Index.cshtml")); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + + var expectedAttribute = "[assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@\"/Views/Index.cshtml\", typeof(SomeNamespace.SomeName), global::SomeNamespace.SomeName.__RouteTemplate)]"; + + var documentNode = new DocumentIntermediateNode() + { + DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind, + Options = codeDocument.CodeGenerationOptions + }; + + var builder = IntermediateNodeBuilder.Create(documentNode); + + var pageDirective = new DirectiveIntermediateNode + { + Directive = PageDirective.Directive, + Children = + { + new LazyIntermediateToken + { + Content = "$\"{AppRoutes.Content}{AppRoutes.Content.Home}\"", + } + }, + }; + + builder.Add(pageDirective); + + var @namespace = new NamespaceDeclarationIntermediateNode + { + Content = "SomeNamespace", + Annotations = + { + [CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace + } + }; + + builder.Push(@namespace); + + var @class = new ClassDeclarationIntermediateNode + { + ClassName = "SomeName", + Annotations = + { + [CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass, + } + }; + + builder.Add(@class); + + // Act + ProjectEngine.ExecutePass(codeDocument, documentNode); + + // Assert + Assert.Collection(documentNode.Children, + node => Assert.Same(pageDirective, node), + node => + { + var csharpCode = Assert.IsType(node); + var token = Assert.IsAssignableFrom(Assert.Single(csharpCode.Children)); + Assert.Equal(TokenKind.CSharp, token.Kind); + Assert.Equal(expectedAttribute, token.Content); + }, + node => Assert.Same(@namespace, node)); + } } diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/IntegrationTests/CodeGenerationIntegrationTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/IntegrationTests/CodeGenerationIntegrationTest.cs index 4f0c1e71fab..f526cc28304 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -165,7 +165,7 @@ public void MalformedPageDirective_Runtime() AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument()); var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; - Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); + Assert.Equal("RZ1046", Assert.Single(diagnotics).Id); } [Fact] @@ -620,7 +620,7 @@ public void MalformedPageDirective_DesignTime() AssertSourceMappingsMatchBaseline(compiled.CodeDocument); var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; - Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); + Assert.Equal("RZ1046", Assert.Single(diagnotics).Id); } [Fact] diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/PageDirectiveTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/PageDirectiveTest.cs index 742fe8f12b3..53953bc37de 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/PageDirectiveTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/PageDirectiveTest.cs @@ -31,10 +31,9 @@ public void TryGetPageDirective_ReturnsTrue_IfPageIsMalformed() var documentNode = processor.GetDocumentNode(); // Act - var result = PageDirective.TryGetPageDirective(documentNode, out var pageDirective); + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); // Assert - Assert.True(result); Assert.Equal("some-route-template", pageDirective.RouteTemplate); Assert.NotNull(pageDirective.DirectiveNode); } @@ -51,10 +50,9 @@ public void TryGetPageDirective_ReturnsTrue_IfPageIsImported() var documentNode = processor.GetDocumentNode(); // Act - var result = PageDirective.TryGetPageDirective(documentNode, out var pageDirective); + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); // Assert - Assert.True(result); Assert.Null(pageDirective.RouteTemplate); } @@ -87,10 +85,9 @@ public void TryGetPageDirective_ReturnsTrue_IfPageDoesStartWithDirective() var documentNode = processor.GetDocumentNode(); // Act - var result = PageDirective.TryGetPageDirective(documentNode, out var pageDirective); + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); // Assert - Assert.True(result); Assert.Null(pageDirective.RouteTemplate); Assert.NotNull(pageDirective.DirectiveNode); } @@ -106,10 +103,9 @@ public void TryGetPageDirective_ReturnsTrue_IfContentHasDirective() var documentNode = processor.GetDocumentNode(); // Act - var result = PageDirective.TryGetPageDirective(documentNode, out var pageDirective); + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); // Assert - Assert.True(result); Assert.Null(pageDirective.RouteTemplate); } @@ -124,10 +120,9 @@ public void TryGetPageDirective_ParsesRouteTemplate() var documentNode = processor.GetDocumentNode(); // Act - var result = PageDirective.TryGetPageDirective(documentNode, out var pageDirective); + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); // Assert - Assert.True(result); Assert.Equal("some-route-template", pageDirective.RouteTemplate); } } diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs index 21caff1347d..817925013d1 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs @@ -18,6 +18,14 @@ public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_Incomplete #pragma warning disable 219 private void __RazorDirectiveTokenHelpers__() { ((global::System.Action)(() => { +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml" + + +#line default +#line hidden + } + ))(); + ((global::System.Action)(() => { #line 8 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml" diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt index cedd71c9588..abc23e63ca3 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt @@ -1,6 +1,6 @@ TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,1): Error RZ2001: The 'page' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,1): Error RZ2001: The 'page' directive may only occur once per document. -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,7): Error RZ1013: The 'model' directive expects a type name. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,1): Error RZ2001: The 'model' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,8): Error RZ1013: The 'model' directive expects a type name. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt index 8831c20bbbc..16a228ab451 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt @@ -23,6 +23,7 @@ DirectiveToken - (617:12,14 [96] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (729:13,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (832:14,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (100:3,6 [0] IncompleteDirectives.cshtml) - DirectiveToken - (128:7,7 [0] IncompleteDirectives.cshtml) - DirectiveToken - (149:10,8 [0] IncompleteDirectives.cshtml) - DirectiveToken - (159:11,8 [17] IncompleteDirectives.cshtml) - MyService @@ -37,6 +38,7 @@ HtmlContent - (85:1,0 [2] IncompleteDirectives.cshtml) LazyIntermediateToken - (85:1,0 [2] IncompleteDirectives.cshtml) - Html - \n MalformedDirective - (94:3,0 [8] IncompleteDirectives.cshtml) - page + DirectiveToken - (100:3,6 [0] IncompleteDirectives.cshtml) - MalformedDirective - (102:4,0 [6] IncompleteDirectives.cshtml) - page HtmlContent - (108:4,6 [5] IncompleteDirectives.cshtml) LazyIntermediateToken - (108:4,6 [5] IncompleteDirectives.cshtml) - Html - "\n\n diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt index cd1a6b7dc5f..5586c2e2904 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt @@ -1,35 +1,40 @@ -Source Location: (128:7,7 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) +Source Location: (100:3,6 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || Generated Location: (813:21,0 [0] ) || +Source Location: (128:7,7 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) +|| +Generated Location: (1011:29,0 [0] ) +|| + Source Location: (149:10,8 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (1012:29,0 [0] ) +Generated Location: (1210:37,0 [0] ) || Source Location: (159:11,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) |MyService| -Generated Location: (1211:37,0 [17] ) +Generated Location: (1409:45,0 [17] ) |MyService| Source Location: (203:14,11 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (1450:45,0 [0] ) +Generated Location: (1648:53,0 [0] ) || Source Location: (119:6,6 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (1910:60,6 [0] ) +Generated Location: (2108:68,6 [0] ) || Source Location: (139:9,7 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (2047:65,7 [0] ) +Generated Location: (2245:73,7 [0] ) || Source Location: (190:13,10 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (2187:70,10 [0] ) +Generated Location: (2385:78,10 [0] ) || diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt index cedd71c9588..abc23e63ca3 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt @@ -1,6 +1,6 @@ TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,1): Error RZ2001: The 'page' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,1): Error RZ2001: The 'page' directive may only occur once per document. -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,7): Error RZ1013: The 'model' directive expects a type name. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,1): Error RZ2001: The 'model' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,8): Error RZ1013: The 'model' directive expects a type name. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt index d545eb7abd8..2063c6d0a59 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt @@ -20,6 +20,7 @@ CSharpCode - IntermediateToken - - CSharp - EndContext(); MalformedDirective - (94:3,0 [8] IncompleteDirectives.cshtml) - page + DirectiveToken - (100:3,6 [0] IncompleteDirectives.cshtml) - MalformedDirective - (102:4,0 [6] IncompleteDirectives.cshtml) - page CSharpCode - IntermediateToken - - CSharp - BeginContext(108, 5, true); diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt index 55fa2d01a4d..0be5b5ec7e6 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt @@ -1 +1 @@ -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt index 55fa2d01a4d..0be5b5ec7e6 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt @@ -1 +1 @@ -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs index 4470b4d319d..8fa467a0a1a 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -201,7 +201,7 @@ public void MalformedPageDirective_Runtime() AssertLinePragmas(compiled.CodeDocument, designTime: false); var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; - Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); + Assert.Equal("RZ1046", Assert.Single(diagnotics).Id); } [Fact] @@ -1113,7 +1113,7 @@ public void MalformedPageDirective_DesignTime() AssertSourceMappingsMatchBaseline(compiled.CodeDocument); var diagnotics = compiled.CodeDocument.GetCSharpDocument().Diagnostics; - Assert.Equal("RZ1016", Assert.Single(diagnotics).Id); + Assert.Equal("RZ1046", Assert.Single(diagnotics).Id); } [Fact] diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/PageDirectiveTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/PageDirectiveTest.cs index dd005cabcbd..b2fe411f9dd 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/PageDirectiveTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/PageDirectiveTest.cs @@ -109,4 +109,116 @@ public void TryGetPageDirective_ParsesRouteTemplate() // Assert Assert.Equal("some-route-template", pageDirective.RouteTemplate); } + + [Fact] + public void TryGetPageDirective_ParsesExpressionRouteTemplate() + { + // Arrange + var codeDocument = ProjectEngine.CreateCodeDocument("@page AppRoutes.Home"); + var processor = CreateCodeDocumentProcessor(codeDocument); + var documentNode = processor.GetDocumentNode(); + + // Act + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); + + // Assert + Assert.Null(pageDirective.RouteTemplate); + Assert.Equal("AppRoutes.Home", pageDirective.RouteTemplateNode!.Content); + Assert.Null(pageDirective.RouteTemplateToken); + } + + [Fact] + public void TryGetPageDirective_DoesNotParseInterpolatedStringRouteTemplate() + { + // Arrange + var content = "@page $\"some-route-template\""; + var source = TestRazorSourceDocument.Create(content, filePath: "file"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + var documentNode = processor.GetDocumentNode(); + + // Act + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); + + // Assert + Assert.Null(pageDirective.RouteTemplate); + Assert.Null(pageDirective.RouteTemplateNode); + Assert.Null(pageDirective.RouteTemplateToken); + } + + [Fact] + public void TryGetPageDirective_ParsesCSharpExpression() + { + // Arrange + var content = "@page @(AppRoutes.Content + AppRoutes.Content.Home)"; + var source = TestRazorSourceDocument.Create(content, filePath: "file"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + var documentNode = processor.GetDocumentNode(); + + // Act + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); + + // Assert + Assert.Null(pageDirective.RouteTemplate); + Assert.Null(pageDirective.RouteTemplateNode); + Assert.Equal("AppRoutes.Content + AppRoutes.Content.Home", pageDirective.RouteTemplateToken!.Content); + } + + [Fact] + public void TryGetPageDirective_ParsesCSharpExpressionContainingInterpolatedString() + { + // Arrange + var content = "@page @($\"{AppRoutes.Content}{AppRoutes.Content.Home}\")"; + var source = TestRazorSourceDocument.Create(content, filePath: "file"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + var documentNode = processor.GetDocumentNode(); + + // Act + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); + + // Assert + Assert.Null(pageDirective.RouteTemplate); + Assert.Null(pageDirective.RouteTemplateNode); + Assert.Equal("$\"{AppRoutes.Content}{AppRoutes.Content.Home}\"", pageDirective.RouteTemplateToken!.Content); + } + + [Fact] + public void TryGetPageDirective_ParsesCSharpExpressionContainingVerbatimInterpolatedString() + { + // Arrange + var content = "@page @(@$\"{AppRoutes.Content}{AppRoutes.Content.Home}\")"; + var source = TestRazorSourceDocument.Create(content, filePath: "file"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + var documentNode = processor.GetDocumentNode(); + + // Act + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); + + // Assert + Assert.Null(pageDirective.RouteTemplate); + Assert.Null(pageDirective.RouteTemplateNode); + Assert.Equal("@$\"{AppRoutes.Content}{AppRoutes.Content.Home}\"", pageDirective.RouteTemplateToken!.Content); + } + + [Fact] + public void TryGetPageDirective_ParsesCSharpExpressionContainingInterpolatedVerbatimString() + { + // Arrange + var content = "@page @($@\"{AppRoutes.Content}{AppRoutes.Content.Home}\")"; + var source = TestRazorSourceDocument.Create(content, filePath: "file"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + var documentNode = processor.GetDocumentNode(); + + // Act + Assert.True(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); + + // Assert + Assert.Null(pageDirective.RouteTemplate); + Assert.Null(pageDirective.RouteTemplateNode); + Assert.Equal("$@\"{AppRoutes.Content}{AppRoutes.Content.Home}\"", pageDirective.RouteTemplateToken!.Content); + } } diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/RazorPageDocumentClassifierPassTest.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/RazorPageDocumentClassifierPassTest.cs index 895409162a0..b6ab66bdc15 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/RazorPageDocumentClassifierPassTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/RazorPageDocumentClassifierPassTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; @@ -316,4 +317,109 @@ public void RazorPageDocumentClassifierPass_AddsRouteTemplateMetadata() Assert.Equal("RouteTemplate", attributeNode.Key); Assert.Equal("some-route", attributeNode.Value); } + + [Fact] + public void RazorPageDocumentClassifierPass_AddsRouteTemplateMetadata_UsingConstantExpression_DefinedInSameRazorFile() + { + // Arrange + const string content = $$""" + @page AppRoutes.Home + + @code { + public static class AppRoutes + { + public const string Home = "/index"; + } + } + """; + var source = TestRazorSourceDocument.Create(content, filePath: "ignored", relativePath: "Test.cshtml"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var extensionNodes = documentNode.GetExtensionNodes(); + Assert.Empty(extensionNodes); + + var classNode = documentNode.GetClassNode(); + var routeTemplateConstNode = GetPageRouteTemplateConstNode(classNode); + Assert.Equal("AppRoutes.Home", routeTemplateConstNode!.Initializer); + } + + [Fact] + public void RazorPageDocumentClassifierPass_HandlesIncompleteQuotedStringLiteral() + { + // Arrange + const string content = $$""" + @page " + + @code { + public static class AppRoutes + { + public const string Home = "/index"; + } + } + """; + var source = TestRazorSourceDocument.Create(content, filePath: "ignored", relativePath: "Test.cshtml"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var extensionNodes = documentNode.GetExtensionNodes(); + Assert.Empty(extensionNodes); + + var classNode = documentNode.GetClassNode(); + var routeTemplateConstNode = GetPageRouteTemplateConstNode(classNode); + Assert.Null(routeTemplateConstNode); + } + + [Fact] + public void RazorPageDocumentClassifierPass_AddsRouteTemplateMetadata_UsingInterpolatedString() + { + // Arrange + const string content = $$""" + @page @($"{AppRoutes.Content.Parent}{AppRoutes.Content.Home}") + + @code { + public static class AppRoutes + { + public static class Content + { + public const string Parent = "/content"; + public const string Home = "/home"; + } + } + } + """; + var source = TestRazorSourceDocument.Create(content, filePath: "ignored", relativePath: "Test.cshtml"); + var codeDocument = ProjectEngine.CreateCodeDocument(source); + var processor = CreateCodeDocumentProcessor(codeDocument); + + // Act + processor.ExecutePass(); + + // Assert + var documentNode = processor.GetDocumentNode(); + var extensionNodes = documentNode.GetExtensionNodes(); + Assert.Empty(extensionNodes); + + var classNode = documentNode.GetClassNode(); + var routeTemplateConstNode = GetPageRouteTemplateConstNode(classNode); + Assert.NotNull(routeTemplateConstNode); + Assert.Equal("$\"{AppRoutes.Content.Parent}{AppRoutes.Content.Home}\"", routeTemplateConstNode!.Initializer); + } + + private static FieldDeclarationIntermediateNode? GetPageRouteTemplateConstNode(ClassDeclarationIntermediateNode classNode) + { + return classNode.Children + .OfType() + .FirstOrDefault(s => s.FieldName is ViewComponentTypes.PageRouteTemplateFieldName); + } } diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs index 501a793e2b1..1af9d09b251 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.codegen.cs @@ -23,6 +23,16 @@ internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_I private void __RazorDirectiveTokenHelpers__() { ((global::System.Action)(() => { #nullable restore +#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml" + + +#line default +#line hidden +#nullable disable + } + ))(); + ((global::System.Action)(() => { +#nullable restore #line 8 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml" diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt index cedd71c9588..abc23e63ca3 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.diagnostics.txt @@ -1,6 +1,6 @@ TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,1): Error RZ2001: The 'page' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,1): Error RZ2001: The 'page' directive may only occur once per document. -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,7): Error RZ1013: The 'model' directive expects a type name. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,1): Error RZ2001: The 'model' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,8): Error RZ1013: The 'model' directive expects a type name. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt index 8a46de8392f..192b2171646 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.ir.txt @@ -25,6 +25,7 @@ DirectiveToken - (673:12,14 [104] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (793:13,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor DirectiveToken - (904:14,14 [95] ) - global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor + DirectiveToken - (100:3,6 [0] IncompleteDirectives.cshtml) - DirectiveToken - (128:7,7 [0] IncompleteDirectives.cshtml) - DirectiveToken - (149:10,8 [0] IncompleteDirectives.cshtml) - DirectiveToken - (159:11,8 [17] IncompleteDirectives.cshtml) - MyService @@ -39,6 +40,7 @@ HtmlContent - (85:1,0 [2] IncompleteDirectives.cshtml) LazyIntermediateToken - (85:1,0 [2] IncompleteDirectives.cshtml) - Html - \n MalformedDirective - (94:3,0 [8] IncompleteDirectives.cshtml) - page + DirectiveToken - (100:3,6 [0] IncompleteDirectives.cshtml) - MalformedDirective - (102:4,0 [6] IncompleteDirectives.cshtml) - page HtmlContent - (108:4,6 [5] IncompleteDirectives.cshtml) LazyIntermediateToken - (108:4,6 [5] IncompleteDirectives.cshtml) - Html - "\n\n diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt index fc357d92584..65c24944151 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_DesignTime.mappings.txt @@ -1,35 +1,40 @@ -Source Location: (128:7,7 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) +Source Location: (100:3,6 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || Generated Location: (1230:26,0 [0] ) || +Source Location: (128:7,7 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) +|| +Generated Location: (1466:36,0 [0] ) +|| + Source Location: (149:10,8 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (1467:36,0 [0] ) +Generated Location: (1703:46,0 [0] ) || Source Location: (159:11,8 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) |MyService| -Generated Location: (1704:46,0 [17] ) +Generated Location: (1940:56,0 [17] ) |MyService| Source Location: (203:14,11 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (1982:56,0 [0] ) +Generated Location: (2218:66,0 [0] ) || Source Location: (119:6,6 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (2480:73,6 [0] ) +Generated Location: (2716:83,6 [0] ) || Source Location: (139:9,7 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (2655:80,7 [0] ) +Generated Location: (2891:90,7 [0] ) || Source Location: (190:13,10 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml) || -Generated Location: (2833:87,10 [0] ) +Generated Location: (3069:97,10 [0] ) || diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt index cedd71c9588..abc23e63ca3 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.diagnostics.txt @@ -1,6 +1,6 @@ TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,1): Error RZ2001: The 'page' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,1): Error RZ2001: The 'page' directive may only occur once per document. -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(5,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,7): Error RZ1013: The 'model' directive expects a type name. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,1): Error RZ2001: The 'model' directive may only occur once per document. TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,8): Error RZ1013: The 'model' directive expects a type name. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt index 41a10f5292e..adda6a0d29e 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives_Runtime.ir.txt @@ -16,6 +16,7 @@ HtmlContent - (85:1,0 [2] IncompleteDirectives.cshtml) LazyIntermediateToken - (85:1,0 [2] IncompleteDirectives.cshtml) - Html - \n MalformedDirective - (94:3,0 [8] IncompleteDirectives.cshtml) - page + DirectiveToken - (100:3,6 [0] IncompleteDirectives.cshtml) - MalformedDirective - (102:4,0 [6] IncompleteDirectives.cshtml) - page HtmlContent - (108:4,6 [5] IncompleteDirectives.cshtml) LazyIntermediateToken - (108:4,6 [5] IncompleteDirectives.cshtml) - Html - "\n\n diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt index 55fa2d01a4d..0be5b5ec7e6 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_DesignTime.diagnostics.txt @@ -1 +1 @@ -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt index 55fa2d01a4d..0be5b5ec7e6 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective_Runtime.diagnostics.txt @@ -1 +1 @@ -TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1016: The 'page' directive expects a string surrounded by double quotes. +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml(1,7): Error RZ1046: The 'page' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentPageDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentPageDirective.cs index a190050a69a..056ae9061a1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentPageDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentPageDirective.cs @@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components; +// HELP: Is this used anywhere? internal class ComponentPageDirective { public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveDescriptorBuilderExtensions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveDescriptorBuilderExtensions.cs index 5df61bb438e..89b929e83a2 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveDescriptorBuilderExtensions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveDescriptorBuilderExtensions.cs @@ -284,4 +284,21 @@ public static IDirectiveDescriptorBuilder AddIdentifierOrExpression(this IDirect return builder; } + + public static IDirectiveDescriptorBuilder AddOptionalIdentifierOrExpressionOrString(this IDirectiveDescriptorBuilder builder, string name, string description) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Tokens.Add( + DirectiveTokenDescriptor.CreateToken( + DirectiveTokenKind.IdentifierOrExpressionOrString, + optional: true, + name: name, + description: description)); + + return builder; + } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveTokenKind.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveTokenKind.cs index 5004f98b732..73e0e2a9dd6 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveTokenKind.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DirectiveTokenKind.cs @@ -14,5 +14,6 @@ public enum DirectiveTokenKind Attribute, Boolean, GenericTypeConstraint, - IdentifierOrExpression + IdentifierOrExpression, + IdentifierOrExpressionOrString } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs index a805dc68422..22eac1c53c4 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Diagnostics; using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.Intermediate; @@ -53,6 +54,17 @@ private void WriteDesignTimeDirectiveToken(CodeRenderingContext context, DesignT return; } + if (tokenKind is DirectiveTokenKind.IdentifierOrExpressionOrString) + { + // We need to evaluate the kind of content that we have received, + // either an identifier or expression, or a simple string + // This does not support verbatim/interpolated/raw strings + // We simply check the expression's type and reuse the logic below + tokenKind = node.Content.StartsWith('"') + ? DirectiveTokenKind.String + : DirectiveTokenKind.IdentifierOrExpression; + } + // Wrap the directive token in a lambda to isolate variable names. context.CodeWriter .Write("((global::") @@ -215,6 +227,9 @@ private void WriteDesignTimeDirectiveToken(CodeRenderingContext context, DesignT context.CodeWriter.WriteLine(";"); } break; + + case DirectiveTokenKind.IdentifierOrExpressionOrString: + throw new NotSupportedException("This directive token kind should have been handled"); } context.CodeWriter.CurrentIndent = originalIndent; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/FieldDeclarationIntermediateNode.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/FieldDeclarationIntermediateNode.cs index edf127d8056..55e3b4e6a36 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/FieldDeclarationIntermediateNode.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Intermediate/FieldDeclarationIntermediateNode.cs @@ -20,6 +20,8 @@ public sealed class FieldDeclarationIntermediateNode : MemberDeclarationIntermed public string FieldType { get; set; } + public string Initializer { get; set; } + public override void Accept(IntermediateNodeVisitor visitor) { if (visitor == null) @@ -37,5 +39,6 @@ public override void FormatNode(IntermediateNodeFormatter formatter) formatter.WriteProperty(nameof(FieldName), FieldName); formatter.WriteProperty(nameof(FieldType), FieldType); formatter.WriteProperty(nameof(Modifiers), string.Join(" ", Modifiers)); + formatter.WriteProperty(nameof(Initializer), Initializer); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs index 170162650d3..59c2df60f96 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/CSharpCodeParser.cs @@ -1511,7 +1511,8 @@ private void ParseExtensibleDirective(in SyntaxListBuilder buil tokenDescriptor.Kind == DirectiveTokenKind.Attribute || tokenDescriptor.Kind == DirectiveTokenKind.GenericTypeConstraint || tokenDescriptor.Kind == DirectiveTokenKind.Boolean || - tokenDescriptor.Kind == DirectiveTokenKind.IdentifierOrExpression) + tokenDescriptor.Kind == DirectiveTokenKind.IdentifierOrExpression || + tokenDescriptor.Kind == DirectiveTokenKind.IdentifierOrExpressionOrString) { directiveBuilder.Add(OutputTokensAsStatementLiteral()); @@ -1715,6 +1716,36 @@ private void ParseExtensibleDirective(in SyntaxListBuilder buil } break; + + case DirectiveTokenKind.IdentifierOrExpressionOrString: + if (At(SyntaxKind.Transition) && NextIs(SyntaxKind.LeftParenthesis)) + { + AcceptAndMoveNext(); + directiveBuilder.Add(OutputAsMetaCode(Output())); + + var expression = ParseExplicitExpressionBody(); + directiveBuilder.Add(expression); + } + else if (TryParseQualifiedIdentifier(out _)) + { + break; + } + else if (At(SyntaxKind.StringLiteral) && !CurrentToken.ContainsDiagnostics) + { + AcceptAndMoveNext(); + } + else + { + Context.ErrorSink.OnError( + RazorDiagnosticFactory.CreateParsing_DirectiveExpectsIdentifierOrExpressionOrString( + new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive)); + + // Default to a string literal as the missing token's kind + builder.Add(BuildDirective(SyntaxKind.StringLiteral)); + return; + } + break; + } chunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticFactory.cs index 78eba3d9676..2a592d523b1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticFactory.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticFactory.cs @@ -396,6 +396,14 @@ public static RazorDiagnostic CreateParsing_PossibleMisplacedPreprocessorDirecti public static RazorDiagnostic CreateParsing_DefineAndUndefNotAllowed(SourceSpan location) => RazorDiagnostic.Create(Parsing_DefineAndUndefNotAllowed, location); + internal static readonly RazorDiagnosticDescriptor Parsing_DirectiveExpectsIdentifierOrExpressionOrString = + new($"{DiagnosticPrefix}1046", + Resources.DirectiveExpectsIdentifierOrExpressionOrStringLiteral, + RazorDiagnosticSeverity.Error); + + public static RazorDiagnostic CreateParsing_DirectiveExpectsIdentifierOrExpressionOrString(SourceSpan location, string directiveName) + => RazorDiagnostic.Create(Parsing_DirectiveExpectsIdentifierOrExpressionOrString, location, directiveName); + #endregion #region Semantic Errors diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Resources.resx b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Resources.resx index 8d10401bd26..556e97fe591 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Resources.resx +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Resources.resx @@ -616,4 +616,7 @@ Possible C# preprocessor directive is misplaced. C# preprocessor directives must be at the start of the line, except for whitespace. + + The '{0}' directive expects an identifier or explicit razor expression ("@()") or a string surrounded by double quotes. + \ No newline at end of file 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..5fb5043acf6 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 @@ -55,8 +55,8 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte else if (documentNode.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind && PageDirective.TryGetPageDirective(documentNode, out var pageDirective)) { - var escapedRoutePrefix = MakeVerbatimStringLiteral(pageDirective.RouteTemplate); - attribute = $"[assembly:{RazorPageAttribute}({escapedPath}, typeof({generatedTypeName}), {escapedRoutePrefix})]"; + var routeTemplateExpression = ProduceRouteTemplateExpression(generatedTypeName, pageDirective); + attribute = $"[assembly:{RazorPageAttribute}({escapedPath}, typeof({generatedTypeName}), {routeTemplateExpression})]"; } else { @@ -76,6 +76,25 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte documentNode.Children.Insert(index, pageAttribute); } + private static string ProduceRouteTemplateExpression(string generatedTypeName, PageDirective directive) + { + if (directive.RouteTemplate is not null) + { + return MakeVerbatimStringLiteral(directive.RouteTemplate); + } + + if (!string.IsNullOrEmpty(directive.RouteTemplateContent)) + { + // If we have a complex expression assigned to the route template, + // we generate a public const field named __RouteTemplate assigned + // to the route template expression, so we add a reference to that + // since we cannot evaluate the constant expression in this context + return $"global::{generatedTypeName}.{ViewComponentTypes.PageRouteTemplateFieldName}"; + } + + return "null"; + } + private static string MakeVerbatimStringLiteral(string value) { if (value == null) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PageDirective.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PageDirective.cs index 027ebcb409e..6f06f6e14d0 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PageDirective.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/PageDirective.cs @@ -1,11 +1,10 @@ // 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.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; @@ -19,19 +18,33 @@ public class PageDirective DirectiveKind.SingleLine, builder => { - builder.AddOptionalStringToken(RazorExtensionsResources.PageDirective_RouteToken_Name, RazorExtensionsResources.PageDirective_RouteToken_Description); + builder + .AddOptionalIdentifierOrExpressionOrString( + RazorExtensionsResources.PageDirective_RouteToken_Name, + RazorExtensionsResources.PageDirective_RouteToken_Description) + ; builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.Description = RazorExtensionsResources.PageDirective_Description; }); - private PageDirective(string routeTemplate, IntermediateNode directiveNode, SourceSpan? source) + private PageDirective( + string? routeTemplate, DirectiveTokenIntermediateNode? routeTemplateNode, + LazyIntermediateToken? routeTemplateToken, IntermediateNode directiveNode, SourceSpan? source) { RouteTemplate = routeTemplate; + RouteTemplateNode = routeTemplateNode; + RouteTemplateToken = routeTemplateToken; DirectiveNode = directiveNode; Source = source; } - public string RouteTemplate { get; } + public string? RouteTemplate { get; } + + public DirectiveTokenIntermediateNode? RouteTemplateNode { get; } + + public IntermediateToken? RouteTemplateToken { get; } + + public string? RouteTemplateContent => RouteTemplateNode?.Content ?? RouteTemplateToken?.Content; public IntermediateNode DirectiveNode { get; } @@ -48,7 +61,7 @@ public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder build return builder; } - public static bool TryGetPageDirective(DocumentIntermediateNode documentNode, out PageDirective pageDirective) + public static bool TryGetPageDirective(DocumentIntermediateNode documentNode, [NotNullWhen(true)] out PageDirective? pageDirective) { var visitor = new Visitor(); for (var i = 0; i < documentNode.Children.Count; i++) @@ -63,34 +76,55 @@ public static bool TryGetPageDirective(DocumentIntermediateNode documentNode, ou } var tokens = visitor.DirectiveTokens.ToList(); - string routeTemplate = null; - SourceSpan? sourceSpan = null; - if (tokens.Count > 0) + var children = visitor.Children?.ToList(); + DirectiveTokenIntermediateNode? routeTemplateNode = null; + LazyIntermediateToken? routeTemplateLazyToken = null; + + if (tokens is [var firstToken, ..]) + { + routeTemplateNode = firstToken; + } + + if (routeTemplateNode is null && children is [LazyIntermediateToken firstChild, ..]) { - routeTemplate = TrimQuotes(tokens[0].Content); - sourceSpan = tokens[0].Source; + routeTemplateLazyToken = firstChild; } - pageDirective = new PageDirective(routeTemplate, visitor.DirectiveNode, sourceSpan); + var content = routeTemplateNode?.Content ?? routeTemplateLazyToken?.Content; + var source = routeTemplateNode?.Source ?? routeTemplateLazyToken?.Source; + + var routeTemplate = TryGetQuotedContent(content); + var sourceSpan = source; + + Debug.Assert(visitor.DirectiveNode is not null); + + pageDirective = new PageDirective(routeTemplate, routeTemplateNode, + routeTemplateLazyToken, visitor.DirectiveNode, sourceSpan); return true; } - private static string TrimQuotes(string content) + private static string? TryGetQuotedContent(string? content) { // Tokens aren't captured if they're malformed. Therefore, this method will - // always be called with a valid token content. - Debug.Assert(content.Length >= 2); - Debug.Assert(content.StartsWith("\"", StringComparison.Ordinal)); - Debug.Assert(content.EndsWith("\"", StringComparison.Ordinal)); + // always be called with a valid token content. However, we could also + // receive an expression that is not a string literal. We will therefore + // only try to parse the simple string literal case, and otherwise let the + // C# expression parser determine the constant value. + if (content is ['\"', .. var literal, '\"']) + { + return literal; + } - return content.Substring(1, content.Length - 2); + return null; } private class Visitor : IntermediateNodeWalker { - public IntermediateNode DirectiveNode { get; private set; } + public IntermediateNode DirectiveNode { get; private set; } = null!; + + public IEnumerable? DirectiveTokens { get; private set; } - public IEnumerable DirectiveTokens { get; private set; } + public IntermediateNodeCollection? Children { get; private set; } public override void VisitDirective(DirectiveIntermediateNode node) { @@ -98,6 +132,7 @@ public override void VisitDirective(DirectiveIntermediateNode node) { DirectiveNode = node; DirectiveTokens = node.Tokens; + Children = node.Children; } } @@ -107,6 +142,7 @@ public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode { DirectiveNode = node; DirectiveTokens = node.Tokens; + Children = node.Children; } } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorPageDocumentClassifierPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorPageDocumentClassifierPass.cs index 803c7c7b4e8..35ad31fd248 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorPageDocumentClassifierPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/RazorPageDocumentClassifierPass.cs @@ -5,8 +5,10 @@ using System.Diagnostics; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; @@ -110,17 +112,20 @@ protected override void OnDocumentStructureCreated( EnsureValidPageDirective(codeDocument, pageDirective); AddRouteTemplateMetadataAttribute(@namespace, @class, pageDirective); + + AddRouteTemplateConstant(@class, pageDirective); } - private static void AddRouteTemplateMetadataAttribute(NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, PageDirective pageDirective) + private void AddRouteTemplateMetadataAttribute(NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, PageDirective pageDirective) { - if (string.IsNullOrEmpty(pageDirective.RouteTemplate)) + var classIndex = @namespace.Children.IndexOf(@class); + if (classIndex == -1) { return; } - var classIndex = @namespace.Children.IndexOf(@class); - if (classIndex == -1) + // Only generate this attribute on Razor compile-time-known strings + if (pageDirective.RouteTemplate is null) { return; } @@ -136,6 +141,43 @@ private static void AddRouteTemplateMetadataAttribute(NamespaceDeclarationInterm @namespace.Children.Insert(classIndex, metadataAttributeNode); } + private void AddRouteTemplateConstant(ClassDeclarationIntermediateNode @class, PageDirective pageDirective) + { + var content = pageDirective.RouteTemplateContent; + if (content is null) + { + return; + } + + // If we have a constant route template known to the Razor compiler, + // like a simple quoted string, we do not generate an internal constant + // member for the route template + if (pageDirective.RouteTemplate is not null) + { + return; + } + + var routeTemplateConstNode = new FieldDeclarationIntermediateNode + { + Annotations = + { + [ComponentMetadata.Common.IsDesignTimePropertyAccessHelper] = bool.TrueString, + }, + Modifiers = + { + "public", + "const", + }, + FieldName = ViewComponentTypes.PageRouteTemplateFieldName, + FieldType = "string", + Initializer = content, + }; + var constBuilder = IntermediateNodeBuilder.Create(@class); + constBuilder.Push(routeTemplateConstNode); + + @class.Children.Insert(0, routeTemplateConstNode); + } + private void EnsureValidPageDirective(RazorCodeDocument codeDocument, PageDirective pageDirective) { Debug.Assert(pageDirective != null); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypes.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypes.cs index bd312de7a25..c5a55edb274 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypes.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypes.cs @@ -29,6 +29,8 @@ internal static class ViewComponentTypes public const string SyncMethodName = "Invoke"; + public const string PageRouteTemplateFieldName = "__RouteTemplate"; + public static class ViewComponent { public const string Name = "Name"; 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 a69b24b522b..9eed5b596f1 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 @@ -88,7 +88,23 @@ public override void VisitDirectiveToken(DirectiveTokenIntermediateNode node) public override void VisitFieldDeclaration(FieldDeclarationIntermediateNode node) { - WriteContentNode(node, string.Join(" ", node.Modifiers), node.FieldType, node.FieldName); + List entries = + [ + string.Join(" ", node.Modifiers), + node.FieldType, + node.FieldName + ]; + + if (node.Initializer is not null) + { + entries.AddRange( + [ + "=", + node.Initializer + ]); + } + + WriteContentNode(node, entries.ToArray()); } public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)