From ae42d7599d8c598aba6d37d7650655896dc0585a Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Fri, 16 Mar 2018 19:04:49 -0700 Subject: [PATCH 1/3] Show error if page directive is not at the top of file --- .../Properties/Resources.Designer.cs | 14 ++++ .../RazorExtensionsDiagnosticFactory.cs | 14 ++++ .../RazorPageDocumentClassifierPass.cs | 47 ++++++++++++++ .../Resources.resx | 3 + .../CodeGenerationIntegrationTest.cs | 16 +++++ .../RazorPageDocumentClassifierPassTest.cs | 65 +++++++++++++++++++ ...RazorPageWithNoLeadingPageDirective.cshtml | 2 + ...LeadingPageDirective_DesignTime.codegen.cs | 42 ++++++++++++ ...ngPageDirective_DesignTime.diagnostics.txt | 1 + ...thNoLeadingPageDirective_DesignTime.ir.txt | 46 +++++++++++++ ...adingPageDirective_DesignTime.mappings.txt | 0 ...hNoLeadingPageDirective_Runtime.codegen.cs | 41 ++++++++++++ ...adingPageDirective_Runtime.diagnostics.txt | 1 + ...eWithNoLeadingPageDirective_Runtime.ir.txt | 33 ++++++++++ 14 files changed, 325 insertions(+) create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.diagnostics.txt create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.ir.txt create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.mappings.txt create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.diagnostics.txt create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.ir.txt diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs index 8764d1a97a..2c5278ec45 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs @@ -262,6 +262,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions internal static string FormatPageDirectiveCannotBeImported(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("PageDirectiveCannotBeImported"), p0, p1); + /// + /// The '@{0}' directive must exist at the top of the file. Only comments and whitespace are allowed before the '@{0}' directive. + /// + internal static string PageDirectiveMustExistAtTheTopOfFile + { + get => GetString("PageDirectiveMustExistAtTheTopOfFile"); + } + + /// + /// The '@{0}' directive must exist at the top of the file. Only comments and whitespace are allowed before the '@{0}' directive. + /// + internal static string FormatPageDirectiveMustExistAtTheTopOfFile(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("PageDirectiveMustExistAtTheTopOfFile"), p0); + /// /// Mark the page as a Razor Page. /// diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensionsDiagnosticFactory.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensionsDiagnosticFactory.cs index 1e2f6a1f7e..74adb4fe93 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensionsDiagnosticFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensionsDiagnosticFactory.cs @@ -113,5 +113,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions return diagnostic; } + + internal static readonly RazorDiagnosticDescriptor PageDirective_MustExistAtTheTopOfFile = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3906", + () => Resources.PageDirectiveMustExistAtTheTopOfFile, + RazorDiagnosticSeverity.Error); + + public static RazorDiagnostic CreatePageDirective_MustExistAtTheTopOfFile(SourceSpan source) + { + var fileName = Path.GetFileName(source.FilePath); + var diagnostic = RazorDiagnostic.Create(PageDirective_MustExistAtTheTopOfFile, source, PageDirective.Directive.Directive); + + return diagnostic; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs index a4959dded2..63e794fed5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs @@ -11,6 +11,25 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page"; + private static readonly RazorProjectEngine LeadingDirectiveParsingEngine = RazorProjectEngine.Create( + RazorConfiguration.Default, + RazorProjectFileSystem.Create("/"), + builder => + { + for (var i = builder.Phases.Count - 1; i >= 0; i--) + { + var phase = builder.Phases[i]; + builder.Phases.RemoveAt(i); + if (phase is IRazorDocumentClassifierPhase) + { + break; + } + } + + RazorExtensions.Register(builder); + builder.Features.Add(new LeadingDirectiveParserOptionsFeature()); + }); + protected override string DocumentKind => RazorPageDocumentKind; protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) @@ -58,6 +77,34 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions pageDirective.DirectiveNode.Diagnostics.Add( RazorExtensionsDiagnosticFactory.CreatePageDirective_CannotBeImported(pageDirective.DirectiveNode.Source.Value)); } + else + { + // The document contains a page directive and it is not imported. + // We now want to make sure this page directive exists at the top of the file. + // We are going to do that by re-parsing the document until the very first line that is not Razor comment + // or whitespace. We then make sure the page directive still exists in the re-parsed IR tree. + var leadingDirectiveCodeDocument = RazorCodeDocument.Create(codeDocument.Source); + LeadingDirectiveParsingEngine.Engine.Process(leadingDirectiveCodeDocument); + + var leadingDirectiveDocumentIRNode = leadingDirectiveCodeDocument.GetDocumentIntermediateNode(); + if (!PageDirective.TryGetPageDirective(leadingDirectiveDocumentIRNode, out var _)) + { + // The page directive is not the leading directive. Add an error. + // Note: Adding the error to the top-level document node because the directive node will be removed by a later optimization pass. + document.Diagnostics.Add( + RazorExtensionsDiagnosticFactory.CreatePageDirective_MustExistAtTheTopOfFile(pageDirective.DirectiveNode.Source.Value)); + } + } + } + + private class LeadingDirectiveParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorParserOptionsFeature + { + public int Order { get; } + + public void Configure(RazorParserOptionsBuilder options) + { + options.ParseLeadingDirectives = true; + } } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx index 2bf9043ca6..c38bccb7fb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx @@ -171,6 +171,9 @@ The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file. + + The '@{0}' directive must precede all other elements defined in a Razor file. + Mark the page as a Razor Page. diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index bcddca531e..014aec689e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -282,6 +282,14 @@ public class AllTagHelper : {typeof(TagHelper).FullName} RunRuntimeTest(compilation); } + + [Fact] + public void RazorPageWithNoLeadingPageDirective_Runtime() + { + var compilation = BaseCompilation; + + RunRuntimeTest(compilation); + } #endregion #region DesignTime @@ -561,6 +569,14 @@ public class AllTagHelper : {typeof(TagHelper).FullName} RunDesignTimeTest(compilation); } + + [Fact] + public void RazorPageWithNoLeadingPageDirective_DesignTime() + { + var compilation = BaseCompilation; + + RunDesignTimeTest(compilation); + } #endregion private void RunRuntimeTest( diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs index 6a21674f2e..9f8623a332 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; @@ -35,6 +36,70 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions Assert.Equal(expectedDiagnostic, diagnostic); } + [Fact] + public void RazorPageDocumentClassifierPass_LogsErrorIfDirectiveNotAtTopOfFile() + { + // Arrange + var sourceSpan = new SourceSpan( + "Test.cshtml", + absoluteIndex: 14 + Environment.NewLine.Length * 2, + lineIndex: 2, + characterIndex: 0, + length: 5 + Environment.NewLine.Length); + + var expectedDiagnostic = RazorExtensionsDiagnosticFactory.CreatePageDirective_MustExistAtTheTopOfFile(sourceSpan); + var content = @" +@somethingelse +@page +"; + var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(content, "Test.cshtml")); + + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + var documentNode = codeDocument.GetDocumentIntermediateNode(); + var diagnostic = Assert.Single(documentNode.Diagnostics); + Assert.Equal(expectedDiagnostic, diagnostic); + } + + [Fact] + public void RazorPageDocumentClassifierPass_DoesNotLogErrorIfCommentAndWhitespaceBeforeDirective() + { + // Arrange + var content = @" +@* some comment *@ + +@page +"; + var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(content, "Test.cshtml")); + + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + var documentNode = codeDocument.GetDocumentIntermediateNode(); + Assert.Empty(documentNode.Diagnostics); + } + [Fact] public void RazorPageDocumentClassifierPass_SetsDocumentKind() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml new file mode 100644 index 0000000000..5172f8f791 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml @@ -0,0 +1,2 @@ +
Some text here.
+@page diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.codegen.cs new file mode 100644 index 0000000000..ec8a4fd5c9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.codegen.cs @@ -0,0 +1,42 @@ +// +#pragma warning disable 1591 +namespace AspNetCore +{ + #line hidden + using TModel = global::System.Object; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Mvc.ViewFeatures; + public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective : global::Microsoft.AspNetCore.Mvc.RazorPages.Page + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static System.Object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective Model => ViewData.Model; + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.diagnostics.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.diagnostics.txt new file mode 100644 index 0000000000..393b35646e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.diagnostics.txt @@ -0,0 +1 @@ +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml(2,1): Error RZ3906: The '@page' directive must precede all other elements defined in a Razor file. diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.ir.txt new file mode 100644 index 0000000000..c21d9fc82c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.ir.txt @@ -0,0 +1,46 @@ +Document - + NamespaceDeclaration - - AspNetCore + UsingDirective - - TModel = global::System.Object + UsingDirective - (1:0,1 [12] ) - System + UsingDirective - (16:1,1 [32] ) - System.Collections.Generic + UsingDirective - (51:2,1 [17] ) - System.Linq + UsingDirective - (71:3,1 [28] ) - System.Threading.Tasks + UsingDirective - (102:4,1 [30] ) - Microsoft.AspNetCore.Mvc + UsingDirective - (135:5,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures + ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective - global::Microsoft.AspNetCore.Mvc.RazorPages.Page - + DesignTimeDirective - + DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper + DirectiveToken - (294:7,71 [4] ) - Html + DirectiveToken - (308:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper + DirectiveToken - (363:8,63 [4] ) - Json + DirectiveToken - (377:9,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper + DirectiveToken - (431:9,62 [9] ) - Component + DirectiveToken - (450:10,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper + DirectiveToken - (494:10,52 [3] ) - Url + DirectiveToken - (507:11,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider + DirectiveToken - (578:11,79 [23] ) - ModelExpressionProvider + 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 + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static System.Object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + HtmlContent - (0:0,0 [28] RazorPageWithNoLeadingPageDirective.cshtml) + IntermediateToken - (0:0,0 [5] RazorPageWithNoLeadingPageDirective.cshtml) - Html -
+ IntermediateToken - (5:0,5 [15] RazorPageWithNoLeadingPageDirective.cshtml) - Html - Some text here. + IntermediateToken - (20:0,20 [6] RazorPageWithNoLeadingPageDirective.cshtml) - Html -
+ IntermediateToken - (26:0,26 [2] RazorPageWithNoLeadingPageDirective.cshtml) - Html - \n + Inject - + Inject - + Inject - + Inject - + Inject - + CSharpCode - + IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + CSharpCode - + IntermediateToken - - CSharp - public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective Model => ViewData.Model; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_DesignTime.mappings.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.codegen.cs new file mode 100644 index 0000000000..0b0bd358fa --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.codegen.cs @@ -0,0 +1,41 @@ +#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "1d35b023a80ffd4cbf92549aae24d6c47f4af7e5" +// +#pragma warning disable 1591 +[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective), @"mvc.1.0.razor-page", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml")] +[assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml", typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective), null)] +namespace AspNetCore +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Mvc.ViewFeatures; + [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"1d35b023a80ffd4cbf92549aae24d6c47f4af7e5", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml")] + public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective : global::Microsoft.AspNetCore.Mvc.RazorPages.Page + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + BeginContext(0, 28, true); + WriteLiteral("
Some text here.
\r\n"); + EndContext(); + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective Model => ViewData.Model; + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.diagnostics.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.diagnostics.txt new file mode 100644 index 0000000000..393b35646e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.diagnostics.txt @@ -0,0 +1 @@ +TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml(2,1): Error RZ3906: The '@page' directive must precede all other elements defined in a Razor file. diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.ir.txt new file mode 100644 index 0000000000..764b704909 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective_Runtime.ir.txt @@ -0,0 +1,33 @@ +Document - + RazorCompiledItemAttribute - + CSharpCode - + IntermediateToken - - CSharp - [assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPageWithNoLeadingPageDirective.cshtml", typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective), null)] + NamespaceDeclaration - - AspNetCore + UsingDirective - (1:0,1 [14] ) - System + UsingDirective - (16:1,1 [34] ) - System.Collections.Generic + UsingDirective - (51:2,1 [19] ) - System.Linq + UsingDirective - (71:3,1 [30] ) - System.Threading.Tasks + UsingDirective - (102:4,1 [32] ) - Microsoft.AspNetCore.Mvc + UsingDirective - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering + UsingDirective - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures + RazorSourceChecksumAttribute - + ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective - global::Microsoft.AspNetCore.Mvc.RazorPages.Page - + MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync + CSharpCode - + IntermediateToken - - CSharp - BeginContext(0, 28, true); + HtmlContent - (0:0,0 [28] RazorPageWithNoLeadingPageDirective.cshtml) + IntermediateToken - (0:0,0 [5] RazorPageWithNoLeadingPageDirective.cshtml) - Html -
+ IntermediateToken - (5:0,5 [15] RazorPageWithNoLeadingPageDirective.cshtml) - Html - Some text here. + IntermediateToken - (20:0,20 [6] RazorPageWithNoLeadingPageDirective.cshtml) - Html -
+ IntermediateToken - (26:0,26 [2] RazorPageWithNoLeadingPageDirective.cshtml) - Html - \n + CSharpCode - + IntermediateToken - - CSharp - EndContext(); + Inject - + Inject - + Inject - + Inject - + Inject - + CSharpCode - + IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + CSharpCode - + IntermediateToken - - CSharp - public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPageWithNoLeadingPageDirective Model => ViewData.Model; From d1c0ab587c7b6870fc07e267962be1dadf487d96 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 5 Apr 2018 13:23:20 -0700 Subject: [PATCH 2/3] Add support for type parameters to class nodes This change will allow someone extending Razor to use generic type parameters in generated classes. There's no user-level extensibility provided here yet, as in there is no language support for adding type parameters. --- .../ViewComponentTagHelperTargetExtension.cs | 7 ++++++- .../ViewComponentTagHelperTargetExtension.cs | 7 ++++++- .../CodeGeneration/CodeWriterExtensions.cs | 12 ++++++++++-- .../CodeGeneration/DefaultDocumentWriter.cs | 13 +++++++++---- .../ClassDeclarationIntermediateNode.cs | 4 +++- .../Intermediate/TypeParameter.cs | 10 ++++++++++ .../DefaultDocumentWriterTest.cs | 7 ++++++- .../RazorTemplateEngineIntegrationTest.cs | 2 ++ .../GenerateCodeWithConfigureClass.codegen.cs | 2 +- .../IntermediateNodeWriter.cs | 19 +++++++++++++++++-- 10 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Razor.Language/Intermediate/TypeParameter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTagHelperTargetExtension.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTagHelperTargetExtension.cs index e810b2a087..eb426d0607 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTagHelperTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTagHelperTargetExtension.cs @@ -60,7 +60,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X WriteTargetElementString(context.CodeWriter, node.TagHelper); // Initialize declaration. - using (context.CodeWriter.BuildClassDeclaration(PublicModifiers, node.ClassName, TagHelperTypeName, interfaces: null)) + using (context.CodeWriter.BuildClassDeclaration( + PublicModifiers, + node.ClassName, + TagHelperTypeName, + interfaces: null, + typeParameters: null)) { // Add view component helper. context.CodeWriter.WriteVariableDeclaration( diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperTargetExtension.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperTargetExtension.cs index 298fe23e31..89aba13222 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperTargetExtension.cs @@ -60,7 +60,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions WriteTargetElementString(context.CodeWriter, node.TagHelper); // Initialize declaration. - using (context.CodeWriter.BuildClassDeclaration(PublicModifiers, node.ClassName, TagHelperTypeName, interfaces: null)) + using (context.CodeWriter.BuildClassDeclaration( + PublicModifiers, + node.ClassName, + TagHelperTypeName, + interfaces: null, + typeParameters: null)) { // Add view component helper. context.CodeWriter.WriteVariableDeclaration( diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriterExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriterExtensions.cs index 7d8c846ca2..2da2852c64 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriterExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriterExtensions.cs @@ -374,7 +374,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration IList modifiers, string name, string baseType, - IEnumerable interfaces) + IList interfaces, + IList typeParameters) { for (var i = 0; i < modifiers.Count; i++) { @@ -385,8 +386,15 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration writer.Write("class "); writer.Write(name); + if (typeParameters != null && typeParameters.Count > 0) + { + writer.Write("<"); + writer.Write(string.Join(", ", typeParameters)); + writer.Write(">"); + } + var hasBaseType = !string.IsNullOrEmpty(baseType); - var hasInterfaces = interfaces != null && interfaces.Count() > 0; + var hasInterfaces = interfaces != null && interfaces.Count > 0; if (hasBaseType || hasInterfaces) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs index 09ed47b6fa..a47295143d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs @@ -78,8 +78,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { algorithmId = "{8829d00f-11b8-4213-878b-770e8597ac16}"; } - else if (string.Equals(algorithm, HashAlgorithmName.SHA1.Name, StringComparison.Ordinal) || - + else if (string.Equals(algorithm, HashAlgorithmName.SHA1.Name, StringComparison.Ordinal) || + // In 2.0, we didn't actually expose the name of the algorithm, so it's possible we could get null here. // If that's the case, we just assume SHA1 since that's the only thing we supported in 2.0. algorithm == null) @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration } else { - var supportedAlgorithms = string.Join(" ", new string[] + var supportedAlgorithms = string.Join(" ", new string[] { HashAlgorithmName.SHA1.Name, HashAlgorithmName.SHA256.Name @@ -143,7 +143,12 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node) { - using (Context.CodeWriter.BuildClassDeclaration(node.Modifiers, node.ClassName, node.BaseType, node.Interfaces)) + using (Context.CodeWriter.BuildClassDeclaration( + node.Modifiers, + node.ClassName, + node.BaseType, + node.Interfaces, + node.TypeParameters.Select(p => p.ParameterName).ToArray())) { VisitDefault(node); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIntermediateNode.cs index 3804ca8a4d..cf923eb429 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIntermediateNode.cs @@ -16,7 +16,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate public string BaseType { get; set; } - public IList Interfaces { get; set; } + public IList Interfaces { get; set; } = new List(); + + public IList TypeParameters { get; set; } = new List(); public override void Accept(IntermediateNodeVisitor visitor) { diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TypeParameter.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TypeParameter.cs new file mode 100644 index 0000000000..08f953e23a --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TypeParameter.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate +{ + public sealed class TypeParameter + { + public string ParameterName { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DefaultDocumentWriterTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DefaultDocumentWriterTest.cs index 67b03f39b8..3537eea4f6 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DefaultDocumentWriterTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/CodeGeneration/DefaultDocumentWriterTest.cs @@ -218,6 +218,11 @@ namespace TestNamespace }, BaseType = "TestBase", Interfaces = new List { "IFoo", "IBar", }, + TypeParameters = new List + { + new TypeParameter() { ParameterName = "TKey", }, + new TypeParameter() { ParameterName = "TValue", }, + }, ClassName = "TestClass", }); @@ -236,7 +241,7 @@ namespace TestNamespace @"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709"" // #pragma warning disable 1591 -internal class TestClass : TestBase, IFoo, IBar +internal class TestClass : TestBase, IFoo, IBar { } #pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/RazorTemplateEngineIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/RazorTemplateEngineIntegrationTest.cs index ad26823b85..cf547f75e7 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/RazorTemplateEngineIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/IntegrationTests/RazorTemplateEngineIntegrationTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; +using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests @@ -66,6 +67,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests engine.ConfigureClass((document, @class) => { + @class.TypeParameters = new[] { new TypeParameter() { ParameterName = "TValue", }, }; @class.Interfaces = new[] { "global::System.IDisposable" }; @class.BaseType = "CustomBaseType"; }); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.codegen.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.codegen.cs index 2baca26a3f..9c5c3a8a73 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.codegen.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.codegen.cs @@ -5,7 +5,7 @@ namespace Razor { #line hidden [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"38aa8e26c5d2a85c61d8e93fe69dd326fe82671b", @"/TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.cshtml")] - protected internal class MyClass : CustomBaseType, global::System.IDisposable + protected internal class MyClass : CustomBaseType, global::System.IDisposable { #pragma warning disable 1998 public async override global::System.Threading.Tasks.Task ExecuteAsync() diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs index 783d021bf7..4329c0d02d 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/IntermediateNodeWriter.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,7 +13,7 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { // Serializes single IR nodes (shallow). - public class IntermediateNodeWriter : + public class IntermediateNodeWriter : IntermediateNodeVisitor, IExtensionIntermediateNodeVisitor { @@ -32,7 +33,21 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node) { - WriteContentNode(node, string.Join(" ", node.Modifiers), node.ClassName, node.BaseType, string.Join(", ", node.Interfaces ?? new List())); + var entries = new List() + { + string.Join(" ", node.Modifiers), + node.ClassName, + node.BaseType, + string.Join(", ", node.Interfaces ?? Array.Empty()) + }; + + // Avoid adding the type parameters to the baseline if they aren't present. + if (node.TypeParameters != null && node.TypeParameters.Count > 0) + { + entries.Add(string.Join(", ", node.TypeParameters)); + } + + WriteContentNode(node, entries.ToArray()); } public override void VisitCSharpExpressionAttributeValue(CSharpExpressionAttributeValueIntermediateNode node) From 4cd24a2b087ea2ec0ff7cc687cb2ed88080c8e7f Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 5 Apr 2018 14:50:40 -0700 Subject: [PATCH 3/3] Add experimental flag to un-special-case data- This change allows blazor to opt into treating data- attributes the same way as normal attributes in the parser. --- .../Legacy/HtmlMarkupParser.cs | 4 +- .../Legacy/ParserContext.cs | 3 + .../RazorParserFeatureFlags.cs | 21 ++++- .../Legacy/CSharpDirectivesTest.cs | 2 +- .../Legacy/CodeParserTestBase.cs | 8 +- .../Legacy/HtmlAttributeTest.cs | 24 +++++ .../Legacy/MarkupParserTestBase.cs | 9 +- .../Legacy/TagHelperBlockRewriterTest.cs | 10 ++- .../Language/Legacy/ParserTestBase.cs | 88 ++++++++++++------- 9 files changed, 128 insertions(+), 41 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs index 579cdc207c..f1ba089ddd 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs @@ -931,7 +931,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { // First, determine if this is a 'data-' attribute (since those can't use conditional attributes) var name = string.Concat(nameSymbols.Select(s => s.Content)); - var attributeCanBeConditional = !name.StartsWith("data-", StringComparison.OrdinalIgnoreCase); + var attributeCanBeConditional = + Context.FeatureFlags.EXPERIMENTAL_AllowConditionalDataDashAttributes || + !name.StartsWith("data-", StringComparison.OrdinalIgnoreCase); // Accept the whitespace and name Accept(whitespace); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserContext.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserContext.cs index 6035ccc81b..0bd3da36ff 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserContext.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserContext.cs @@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy Source = new SeekableTextReader(chars, source.FilePath); DesignTimeMode = options.DesignTime; + FeatureFlags = options.FeatureFlags; ParseLeadingDirectives = options.ParseLeadingDirectives; Builder = new SyntaxTreeBuilder(); ErrorSink = new ErrorSink(); @@ -33,6 +34,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy public ErrorSink ErrorSink { get; set; } + public RazorParserFeatureFlags FeatureFlags { get; } + public HashSet SeenDirectives { get; } public ITextDocument Source { get; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorParserFeatureFlags.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorParserFeatureFlags.cs index 33324d00bd..7fe1bf74b4 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorParserFeatureFlags.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorParserFeatureFlags.cs @@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language { var allowMinimizedBooleanTagHelperAttributes = false; var allowHtmlCommentsInTagHelpers = false; + var experimental_AllowConditionalDataDashAttributes = false; if (version.CompareTo(RazorLanguageVersion.Version_2_1) >= 0) { @@ -17,24 +18,40 @@ namespace Microsoft.AspNetCore.Razor.Language allowHtmlCommentsInTagHelpers = true; } - return new DefaultRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes, allowHtmlCommentsInTagHelpers); + if (version.CompareTo(RazorLanguageVersion.Experimental) >= 0) + { + experimental_AllowConditionalDataDashAttributes = true; + } + + return new DefaultRazorParserFeatureFlags( + allowMinimizedBooleanTagHelperAttributes, + allowHtmlCommentsInTagHelpers, + experimental_AllowConditionalDataDashAttributes); } public abstract bool AllowMinimizedBooleanTagHelperAttributes { get; } public abstract bool AllowHtmlCommentsInTagHelpers { get; } + public abstract bool EXPERIMENTAL_AllowConditionalDataDashAttributes { get; } + private class DefaultRazorParserFeatureFlags : RazorParserFeatureFlags { - public DefaultRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes, bool allowHtmlCommentsInTagHelpers) + public DefaultRazorParserFeatureFlags( + bool allowMinimizedBooleanTagHelperAttributes, + bool allowHtmlCommentsInTagHelpers, + bool experimental_AllowConditionalDataDashAttributes) { AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes; AllowHtmlCommentsInTagHelpers = allowHtmlCommentsInTagHelpers; + EXPERIMENTAL_AllowConditionalDataDashAttributes = experimental_AllowConditionalDataDashAttributes; } public override bool AllowMinimizedBooleanTagHelperAttributes { get; } public override bool AllowHtmlCommentsInTagHelpers { get; } + + public override bool EXPERIMENTAL_AllowConditionalDataDashAttributes { get; } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs index 82d72c82e0..23cda39e22 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs @@ -1957,7 +1957,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy Block expected, params RazorDiagnostic[] expectedErrors) { - var result = ParseCodeBlock(document, descriptors, designTime: false); + var result = ParseCodeBlock(RazorLanguageVersion.Latest, document, descriptors, designTime: false); EvaluateResults(result, expected, expectedErrors); } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CodeParserTestBase.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CodeParserTestBase.cs index f0af2d1530..923a25a441 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CodeParserTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CodeParserTestBase.cs @@ -9,9 +9,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { internal abstract ISet KeywordSet { get; } - internal override RazorSyntaxTree ParseBlock(string document, IEnumerable directives, bool designTime) + internal override RazorSyntaxTree ParseBlock( + RazorLanguageVersion version, + string document, + IEnumerable directives, + bool designTime) { - return ParseCodeBlock(document, directives, designTime); + return ParseCodeBlock(version, document, directives, designTime); } internal void ImplicitExpressionTest(string input, params RazorDiagnostic[] errors) diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/HtmlAttributeTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/HtmlAttributeTest.cs index 8687be85d3..64686c9fbb 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/HtmlAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/HtmlAttributeTest.cs @@ -559,6 +559,30 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy Assert.Equal(rewritten.Children.Count(), results.Root.Children.Count()); } + [Fact] + public void ConditionalAttributesAreEnabledForDataAttributesWithExperimentalFlag() + { + ParseBlockTest( + RazorLanguageVersion.Experimental, + "", + new MarkupBlock( + new MarkupTagBlock( + Factory.Markup("(" data-foo='", 5, 0, 5), new LocationTagged("'", 20, 0, 20)), + Factory.Markup(" data-foo='").With(SpanChunkGenerator.Null), + new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 16, 0, 16), 16, 0, 16), + new ExpressionBlock( + Factory.CodeTransition(), + Factory.Code("foo") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharactersInternal.NonWhiteSpace))), + Factory.Markup("'").With(SpanChunkGenerator.Null)), + Factory.Markup(">").Accepts(AcceptedCharactersInternal.None)), + new MarkupTagBlock( + Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); + } + [Fact] public void ConditionalAttributesAreDisabledForDataAttributesInBlock() { diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/MarkupParserTestBase.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/MarkupParserTestBase.cs index 096681ed4c..16b1f43c1f 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/MarkupParserTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/MarkupParserTestBase.cs @@ -1,16 +1,19 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - using System.Collections.Generic; namespace Microsoft.AspNetCore.Razor.Language.Legacy { public abstract class MarkupParserTestBase : CodeParserTestBase { - internal override RazorSyntaxTree ParseBlock(string document, IEnumerable directives, bool designTime) + internal override RazorSyntaxTree ParseBlock( + RazorLanguageVersion version, + string document, + IEnumerable directives, + bool designTime) { - return ParseHtmlBlock(document, directives, designTime); + return ParseHtmlBlock(version, document, directives, designTime); } internal virtual void SingleSpanDocumentTest(string document, BlockKindInternal blockKind, SpanKindInternal spanType) diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperBlockRewriterTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperBlockRewriterTest.cs index a970b69539..92a16d3a74 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperBlockRewriterTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/TagHelperBlockRewriterTest.cs @@ -3962,7 +3962,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy .Build(), }; - var featureFlags = new TestRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes: false, allowHtmlCommentsInTagHelper: false); + var featureFlags = new TestRazorParserFeatureFlags(); var expectedOutput = new MarkupBlock( new MarkupTagHelperBlock( @@ -3994,15 +3994,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy private class TestRazorParserFeatureFlags : RazorParserFeatureFlags { - public TestRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes, bool allowHtmlCommentsInTagHelper) + public TestRazorParserFeatureFlags( + bool allowMinimizedBooleanTagHelperAttributes = false, + bool allowHtmlCommentsInTagHelper = false, + bool experimental_AllowConditionalDataDashAttributes = false) { AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes; AllowHtmlCommentsInTagHelpers = allowHtmlCommentsInTagHelper; + EXPERIMENTAL_AllowConditionalDataDashAttributes = experimental_AllowConditionalDataDashAttributes; } public override bool AllowMinimizedBooleanTagHelperAttributes { get; } public override bool AllowHtmlCommentsInTagHelpers { get; } + + public override bool EXPERIMENTAL_AllowConditionalDataDashAttributes { get; } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/ParserTestBase.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/ParserTestBase.cs index 1f9ec8a407..4d80343ff3 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/ParserTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/Legacy/ParserTestBase.cs @@ -32,23 +32,43 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy internal RazorSyntaxTree ParseBlock(string document, bool designTime) { - return ParseBlock(document, null, designTime); + return ParseBlock(RazorLanguageVersion.Latest, document, designTime); } - internal abstract RazorSyntaxTree ParseBlock(string document, IEnumerable directives, bool designTime); - - internal virtual RazorSyntaxTree ParseDocument(string document, bool designTime = false) + internal RazorSyntaxTree ParseBlock(RazorLanguageVersion version, string document, bool designTime) { - return ParseDocument(document, null, designTime); + return ParseBlock(version, document, null, designTime); } - internal virtual RazorSyntaxTree ParseDocument(string document, IEnumerable directives, bool designTime = false) + internal RazorSyntaxTree ParseBlock(string document, IEnumerable directives, bool designTime) + { + return ParseBlock(RazorLanguageVersion.Latest, document, directives, designTime); + } + + internal abstract RazorSyntaxTree ParseBlock(RazorLanguageVersion version, string document, IEnumerable directives, bool designTime); + + internal RazorSyntaxTree ParseDocument(string document, bool designTime = false) + { + return ParseDocument(RazorLanguageVersion.Latest, document, designTime); + } + + internal RazorSyntaxTree ParseDocument(RazorLanguageVersion version, string document, bool designTime = false) + { + return ParseDocument(version, document, null, designTime); + } + + internal RazorSyntaxTree ParseDocument(string document, IEnumerable directives, bool designTime = false) + { + return ParseDocument(RazorLanguageVersion.Latest, document, directives, designTime); + } + + internal virtual RazorSyntaxTree ParseDocument(RazorLanguageVersion version, string document, IEnumerable directives, bool designTime = false) { directives = directives ?? Array.Empty(); var source = TestRazorSourceDocument.Create(document, filePath: null); - var options = CreateParserOptions(directives, designTime); + var options = CreateParserOptions(version, directives, designTime); var context = new ParserContext(source, options); var codeParser = new CSharpCodeParser(directives, context); @@ -73,12 +93,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy return syntaxTree; } - internal virtual RazorSyntaxTree ParseHtmlBlock(string document, IEnumerable directives, bool designTime = false) + internal virtual RazorSyntaxTree ParseHtmlBlock(RazorLanguageVersion version, string document, IEnumerable directives, bool designTime = false) { directives = directives ?? Array.Empty(); var source = TestRazorSourceDocument.Create(document, filePath: null); - var options = CreateParserOptions(directives, designTime); + var options = CreateParserOptions(version, directives, designTime); var context = new ParserContext(source, options); var parser = new HtmlMarkupParser(context); @@ -95,12 +115,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy return RazorSyntaxTree.Create(root, source, diagnostics, options); } - internal virtual RazorSyntaxTree ParseCodeBlock(string document, bool designTime = false) + internal RazorSyntaxTree ParseCodeBlock(string document, bool designTime = false) { - return ParseCodeBlock(document, Enumerable.Empty(), designTime); + return ParseCodeBlock(RazorLanguageVersion.Latest, document, Enumerable.Empty(), designTime); } internal virtual RazorSyntaxTree ParseCodeBlock( + RazorLanguageVersion version, string document, IEnumerable directives, bool designTime) @@ -108,7 +129,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy directives = directives ?? Array.Empty(); var source = TestRazorSourceDocument.Create(document, filePath: null); - var options = CreateParserOptions(directives, designTime); + var options = CreateParserOptions(version, directives, designTime); var context = new ParserContext(source, options); var parser = new CSharpCodeParser(directives, context); @@ -152,6 +173,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy ParseBlockTest(document, null, designTime, expectedErrors); } + internal virtual void ParseBlockTest(RazorLanguageVersion version, string document, Block expectedRoot) + { + ParseBlockTest(version, document, expectedRoot, false, null); + } + internal virtual void ParseBlockTest(string document, Block expectedRoot) { ParseBlockTest(document, expectedRoot, false, null); @@ -182,9 +208,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy ParseBlockTest(document, null, expected, designTime, expectedErrors); } + internal virtual void ParseBlockTest(RazorLanguageVersion version, string document, Block expected, bool designTime, params RazorDiagnostic[] expectedErrors) + { + ParseBlockTest(version, document, null, expected, designTime, expectedErrors); + } + internal virtual void ParseBlockTest(string document, IEnumerable directives, Block expected, bool designTime, params RazorDiagnostic[] expectedErrors) { - var result = ParseBlock(document, directives, designTime); + ParseBlockTest(RazorLanguageVersion.Latest, document, directives, expected, designTime, expectedErrors); + } + + internal virtual void ParseBlockTest(RazorLanguageVersion version, string document, IEnumerable directives, Block expected, bool designTime, params RazorDiagnostic[] expectedErrors) + { + var result = ParseBlock(version, document, directives, designTime); if (FixupSpans) { @@ -604,24 +640,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy return block.Build(); } - private static RazorParserOptions CreateParserOptions(IEnumerable directives, bool designTime) + private static RazorParserOptions CreateParserOptions( + RazorLanguageVersion version, + IEnumerable directives, + bool designTime) { - if (designTime) - { - return RazorParserOptions.CreateDesignTime(ConfigureOptions); - } - else - { - return RazorParserOptions.Create(ConfigureOptions); - } - - void ConfigureOptions(RazorParserOptionsBuilder builder) - { - foreach (var directive in directives) - { - builder.Directives.Add(directive); - } - } + return new DefaultRazorParserOptions( + directives.ToArray(), + designTime, + parseLeadingDirectives: false, + version: version); } private class IgnoreOutputBlock : Block