diff --git a/NuGet.config b/NuGet.config index c575f309fa0..0f94e3b8470 100644 --- a/NuGet.config +++ b/NuGet.config @@ -41,9 +41,6 @@ --> - - - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 3d75f97f395..264d9ef9ded 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -23,6 +23,10 @@ https://github.com/dotnet/roslyn f6725f6f04ce03574fcf89faa4b20b72ef83e4dd + + https://github.com/dotnet/roslyn + f6725f6f04ce03574fcf89faa4b20b72ef83e4dd + https://github.com/dotnet/roslyn f6725f6f04ce03574fcf89faa4b20b72ef83e4dd diff --git a/eng/Versions.props b/eng/Versions.props index 52bbe9f007d..82fafac8315 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -55,6 +55,7 @@ 4.7.0-2.23301.2 4.7.0-2.23301.2 4.7.0-2.23301.2 + 4.7.0-2.23301.2 4.7.0-2.23301.2 4.7.0-2.23301.2 4.7.0-2.23301.2 diff --git a/src/Compiler/Directory.Packages.props b/src/Compiler/Directory.Packages.props index 40aa45d5718..6da3e951670 100644 --- a/src/Compiler/Directory.Packages.props +++ b/src/Compiler/Directory.Packages.props @@ -11,6 +11,7 @@ + diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.csproj b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.csproj index 682739d6978..2f041fd36fe 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.csproj +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerationOptions.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerationOptions.cs index 1ba18073342..354ecfac433 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerationOptions.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerationOptions.cs @@ -25,7 +25,7 @@ internal sealed class RazorSourceGenerationOptions : IEquatable - /// Gets a flag that determines if localized component names should be supported.. + /// Gets a flag that determines if localized component names should be supported. /// public bool SupportLocalizedComponentNames { get; set; } = false; diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs index 4fa23ad802d..8c4fa6479ac 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs @@ -28,6 +28,12 @@ private static bool GetSuppressionStatus(AnalyzerConfigOptionsProvider optionsPr && suppressRazorSourceGenerator == "true"; } + private static bool GetHostOutputsEnabledStatus(AnalyzerConfigOptionsProvider optionsProvider, CancellationToken _) + { + return optionsProvider.GlobalOptions.TryGetValue("build_property.EnableRazorHostOutputs", out var enableRazorHostOutputs) + && enableRazorHostOutputs == "true"; + } + private static (RazorSourceGenerationOptions?, Diagnostic?) ComputeRazorSourceGeneratorOptions((AnalyzerConfigOptionsProvider, ParseOptions) pair, CancellationToken ct) { Log.ComputeRazorSourceGeneratorOptions(); diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs index 4fd80d7e3b6..f555839bcf0 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler; namespace Microsoft.NET.Sdk.Razor.SourceGenerators { @@ -24,11 +25,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var compilation = context.CompilationProvider; // determine if we should suppress this run and filter out all the additional files if so - var isGeneratorSuppressed = context.AnalyzerConfigOptionsProvider.Select(GetSuppressionStatus); + var isGeneratorSuppressed = analyzerConfigOptions.Select(GetSuppressionStatus); var additionalTexts = context.AdditionalTextsProvider - .Combine(isGeneratorSuppressed) - .Where(pair => !pair.Right) - .Select((pair, _) => pair.Left); + .Combine(isGeneratorSuppressed) + .Where(pair => !pair.Right) + .Select((pair, _) => pair.Left); var razorSourceGeneratorOptions = analyzerConfigOptions .Combine(parseOptions) @@ -68,7 +69,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Combine(razorSourceGeneratorOptions) .Select(static (pair, _) => { - var ((sourceItem, importFiles), razorSourceGeneratorOptions) = pair; RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.FilePath); @@ -204,26 +204,39 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return allTagHelpers; }); - var generatedOutput = sourceItems + var withOptions = sourceItems .Combine(importFiles.Collect()) .Combine(allTagHelpers) - .Combine(razorSourceGeneratorOptions) - .Select(static (pair, _) => + .Combine(razorSourceGeneratorOptions); + + var withOptionsDesignTime = withOptions + .Combine(analyzerConfigOptions.Select(GetHostOutputsEnabledStatus)) + .Where(pair => pair.Right) + .Select((pair, _) => pair.Left); + + IncrementalValuesProvider<(string, RazorCodeDocument)> processed(bool designTime) => (designTime ? withOptionsDesignTime : withOptions) + .Select((pair, _) => { var (((sourceItem, imports), allTagHelpers), razorSourceGeneratorOptions) = pair; - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(sourceItem.FilePath); - - // Add a generated suffix so tools, such as coverlet, consider the file to be generated - var hintName = GetIdentifierFromPath(sourceItem.RelativePhysicalPath) + ".g.cs"; + var kind = designTime ? "DesignTime" : "Runtime"; + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(sourceItem.FilePath, kind); var projectEngine = GetGenerationProjectEngine(allTagHelpers, sourceItem, imports, razorSourceGeneratorOptions); - var codeDocument = projectEngine.Process(sourceItem); - var csharpDocument = codeDocument.GetCSharpDocument(); + var codeDocument = designTime + ? projectEngine.ProcessDesignTime(sourceItem) + : projectEngine.Process(sourceItem); + + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(sourceItem.FilePath, kind); + return (filePath: sourceItem.RelativePhysicalPath, codeDocument); + }); - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(sourceItem.FilePath); - return (hintName, csharpDocument); + var csharpDocuments = processed(designTime: false) + .Select(static (pair, _) => + { + var (filePath, document) = pair; + return (filePath, csharpDocument: document.GetCSharpDocument()); }) .WithLambdaComparer(static (a, b) => { @@ -236,9 +249,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return string.Equals(a.csharpDocument.GeneratedCode, b.csharpDocument.GeneratedCode, StringComparison.Ordinal); }, static a => StringComparer.Ordinal.GetHashCode(a.csharpDocument)); - context.RegisterSourceOutput(generatedOutput, static (context, pair) => + context.RegisterImplementationSourceOutput(csharpDocuments, static (context, pair) => { - var (hintName, csharpDocument) = pair; + var (filePath, csharpDocument) = pair; + + // Add a generated suffix so tools, such as coverlet, consider the file to be generated + var hintName = GetIdentifierFromPath(filePath) + ".g.cs"; + RazorSourceGeneratorEventSource.Log.AddSyntaxTrees(hintName); for (var i = 0; i < csharpDocument.Diagnostics.Count; i++) { @@ -249,6 +266,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.AddSource(hintName, csharpDocument.GeneratedCode); }); + + context.RegisterHostOutput(processed(designTime: true), static (context, pair, _) => + { + var (filePath, document) = pair; + var hintName = GetIdentifierFromPath(filePath); + context.AddOutput(hintName + ".rsg.cs", document.GetCSharpDocument().GeneratedCode); + context.AddOutput(hintName + ".rsg.html", document.GetHtmlDocument().GeneratedCode); + }); } } } diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGeneratorEventSource.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGeneratorEventSource.cs index 758d0069b8b..8ef7ac0f98a 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGeneratorEventSource.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGeneratorEventSource.cs @@ -42,11 +42,11 @@ private RazorSourceGeneratorEventSource() { } private const int RazorCodeGenerateStartId = 10; [Event(RazorCodeGenerateStartId, Level = EventLevel.Informational)] - public void RazorCodeGenerateStart(string file) => WriteEvent(RazorCodeGenerateStartId, file); + public void RazorCodeGenerateStart(string file, string kind) => WriteEvent(RazorCodeGenerateStartId, file, kind); private const int RazorCodeGenerateStopId = 11; [Event(RazorCodeGenerateStopId, Level = EventLevel.Informational)] - public void RazorCodeGenerateStop(string file) => WriteEvent(RazorCodeGenerateStopId, file); + public void RazorCodeGenerateStop(string file, string kind) => WriteEvent(RazorCodeGenerateStopId, file, kind); private const int AddSyntaxTreesId = 12; [Event(AddSyntaxTreesId, Level = EventLevel.Informational)] diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs index ac3400bba12..b98a89d8fde 100644 --- a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs @@ -97,6 +97,99 @@ public class MyModel { } Assert.Equal(2, result.GeneratedSources.Length); } + [Fact] + public async Task SourceGenerator_RazorFiles_DesignTime() + { + // Arrange + var project = CreateTestProject(new() + { + ["Pages/Index.razor"] = "

Hello world

", + }); + + var compilation = await project.GetCompilationAsync(); + var (driver, additionalTexts, optionsProvider) = await GetDriverWithAdditionalTextAndProviderAsync(project, hostOutputs: true); + + // Enable design-time. + var options = optionsProvider.Clone(); + options.TestGlobalOptions["build_property.RazorDesignTime"] = "true"; + options.TestGlobalOptions["build_property.EnableRazorHostOutputs"] = "true"; + var driver2 = driver.WithUpdatedAnalyzerConfigOptions(options); + + var result = RunGenerator(compilation!, ref driver2); + + var pageOutput = +@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3"" +// +#pragma warning disable 1591 +namespace MyApp.Pages +{ + #line hidden + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + __builder.AddMarkupContent(0, ""

Hello world

""); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 +"; + result.VerifyPageOutput(pageOutput); + + result.VerifyHostOutput( + (@"Pages_Index_razor.rsg.cs", @"// +#pragma warning disable 1591 +namespace MyApp.Pages +{ + #line hidden + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 +"), + (@"Pages_Index_razor.rsg.html", @"

Hello world

")); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedSources); + + // Enable design-time without host outputs. + options = optionsProvider.Clone(); + options.TestGlobalOptions["build_property.RazorDesignTime"] = "true"; + driver2 = driver.WithUpdatedAnalyzerConfigOptions(options); + + result = RunGenerator(compilation!, ref driver2); + result.VerifyPageOutput(pageOutput); + result.VerifyHostOutput(); + + Assert.Empty(result.Diagnostics); + Assert.Single(result.GeneratedSources); + } + [Fact] public async Task SourceGeneratorEvents_RazorFiles_Works() { @@ -164,70 +257,21 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. Assert.Collection(eventListener.Events, e => Assert.Equal("ComputeRazorSourceGeneratorOptions", e.EventName), - e => - { - Assert.Equal("GenerateDeclarationCodeStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("GenerateDeclarationCodeStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("GenerateDeclarationCodeStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("GenerateDeclarationCodeStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, + e => e.AssertSingleItem("GenerateDeclarationCodeStart", "/Pages/Index.razor"), + e => e.AssertSingleItem("GenerateDeclarationCodeStop", "/Pages/Index.razor"), + e => e.AssertSingleItem("GenerateDeclarationCodeStart", "/Pages/Counter.razor"), + e => e.AssertSingleItem("GenerateDeclarationCodeStop", "/Pages/Counter.razor"), e => Assert.Equal("DiscoverTagHelpersFromCompilationStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromReferencesStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromReferencesStop", e.EventName), - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Index_razor.g.cs", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Counter_razor.g.cs", file); - }); + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Index.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Index.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Counter.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Counter.razor", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Index_razor.g.cs"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Counter_razor.g.cs") + ); } [Fact] @@ -413,36 +457,12 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. Assert.Equal(2, result.GeneratedSources.Length); Assert.Collection(eventListener.Events, - e => - { - Assert.Equal("GenerateDeclarationCodeStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("GenerateDeclarationCodeStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Counter_razor.g.cs", file); - }); + e => e.AssertSingleItem("GenerateDeclarationCodeStart", "/Pages/Counter.razor"), + e => e.AssertSingleItem("GenerateDeclarationCodeStop", "/Pages/Counter.razor"), + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Counter.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Counter.razor", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Counter_razor.g.cs") + ); } [Fact] @@ -787,38 +807,14 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. Assert.Equal(2, result.GeneratedSources.Length); Assert.Collection(eventListener.Events, - e => - { - Assert.Equal("GenerateDeclarationCodeStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("GenerateDeclarationCodeStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, + e => e.AssertSingleItem("GenerateDeclarationCodeStart", "/Pages/Counter.razor"), + e => e.AssertSingleItem("GenerateDeclarationCodeStop", "/Pages/Counter.razor"), e => Assert.Equal("DiscoverTagHelpersFromCompilationStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName), - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Counter_razor.g.cs", file); - }); + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Counter.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Counter.razor", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Counter_razor.g.cs") + ); } [Fact] @@ -967,50 +963,16 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. Assert.Equal(2, result.GeneratedSources.Length); Assert.Collection(eventListener.Events, - e => - { - Assert.Equal("GenerateDeclarationCodeStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("GenerateDeclarationCodeStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, + e => e.AssertSingleItem("GenerateDeclarationCodeStart", "/Pages/Counter.razor"), + e => e.AssertSingleItem("GenerateDeclarationCodeStop", "/Pages/Counter.razor"), e => Assert.Equal("DiscoverTagHelpersFromCompilationStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName), - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Counter_razor.g.cs", file); - }); + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Index.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Index.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Counter.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Counter.razor", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Counter_razor.g.cs") + ); } [Fact] @@ -1145,36 +1107,12 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromReferencesStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromReferencesStop", e.EventName), - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Counter.razor", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Index_razor.g.cs", file); - }); + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Index.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Index.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Counter.razor", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Counter.razor", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Index_razor.g.cs") + ); // Verify caching eventListener.Events.Clear(); @@ -1330,42 +1268,13 @@ internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Ra e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromReferencesStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromReferencesStop", e.EventName), - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.cshtml", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.cshtml", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Views/Shared/_Layout.cshtml", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Views/Shared/_Layout.cshtml", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Index_cshtml.g.cs", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Views_Shared__Layout_cshtml.g.cs", file); - }); + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Index.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Index.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStart", "/Views/Shared/_Layout.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Views/Shared/_Layout.cshtml", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Index_cshtml.g.cs"), + e => e.AssertSingleItem("AddSyntaxTrees", "Views_Shared__Layout_cshtml.g.cs") + ); } [Fact, WorkItem("https://github.com/dotnet/razor/issues/7049")] @@ -1693,24 +1602,10 @@ internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Ra Assert.Equal(2, result.GeneratedSources.Length); Assert.Collection(eventListener.Events, - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Views/Shared/_Layout.cshtml", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Views/Shared/_Layout.cshtml", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Views_Shared__Layout_cshtml.g.cs", file); - }); + e => e.AssertPair("RazorCodeGenerateStart", "/Views/Shared/_Layout.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Views/Shared/_Layout.cshtml", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Views_Shared__Layout_cshtml.g.cs") + ); } [Fact] @@ -2028,36 +1923,12 @@ public override void Process(TagHelperContext context, TagHelperOutput output) Assert.Collection(eventListener.Events, e => Assert.Equal("DiscoverTagHelpersFromCompilationStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName), - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.cshtml", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Pages/Index.cshtml", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStart", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Views/Shared/_Layout.cshtml", file); - }, - e => - { - Assert.Equal("RazorCodeGenerateStop", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("/Views/Shared/_Layout.cshtml", file); - }, - e => - { - Assert.Equal("AddSyntaxTrees", e.EventName); - var file = Assert.Single(e.Payload); - Assert.Equal("Pages_Index_cshtml.g.cs", file); - }); + e => e.AssertPair("RazorCodeGenerateStart", "/Pages/Index.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Pages/Index.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStart", "/Views/Shared/_Layout.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "/Views/Shared/_Layout.cshtml", "Runtime"), + e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Index_cshtml.g.cs") + ); } [Fact] diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs index 496ab7c613a..1c4da81ea38 100644 --- a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTestsBase.cs @@ -30,6 +30,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.DependencyModel.Resolution; @@ -56,10 +57,11 @@ protected static async ValueTask GetDriverAsync(Project project return (result.Item1, result.Item2); } - protected static async ValueTask<(GeneratorDriver, ImmutableArray, TestAnalyzerConfigOptionsProvider)> GetDriverWithAdditionalTextAndProviderAsync(Project project, Action? configureGlobalOptions = null) + protected static async ValueTask<(GeneratorDriver, ImmutableArray, TestAnalyzerConfigOptionsProvider)> GetDriverWithAdditionalTextAndProviderAsync(Project project, Action? configureGlobalOptions = null, bool hostOutputs = false) { var razorSourceGenerator = new RazorSourceGenerator().AsSourceGenerator(); - var driver = (GeneratorDriver)CSharpGeneratorDriver.Create(new[] { razorSourceGenerator }, parseOptions: (CSharpParseOptions)project.ParseOptions!, driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, true)); + var disabledOutputs = hostOutputs ? IncrementalGeneratorOutputKind.None : (IncrementalGeneratorOutputKind)0b100000; + var driver = (GeneratorDriver)CSharpGeneratorDriver.Create(new[] { razorSourceGenerator }, parseOptions: (CSharpParseOptions)project.ParseOptions!, driverOptions: new GeneratorDriverOptions(disabledOutputs, true)); var optionsProvider = new TestAnalyzerConfigOptionsProvider(); optionsProvider.TestGlobalOptions["build_property.RazorConfiguration"] = "Default"; @@ -409,7 +411,7 @@ public static GeneratorRunResult VerifyPageOutput(this GeneratorRunResult result { if (expectedOutput.Length == 1 && string.IsNullOrWhiteSpace(expectedOutput[0])) { - Assert.True(false, GenerateExpectedOutput(result)); + Assert.True(false, GenerateExpectedPageOutput(result)); } else { @@ -424,6 +426,29 @@ public static GeneratorRunResult VerifyPageOutput(this GeneratorRunResult result return result; } + public static GeneratorRunResult VerifyHostOutput(this GeneratorRunResult result, params (string hintName, string text)[] expectedOutputs) + { + if (expectedOutputs.Length == 1 && string.IsNullOrWhiteSpace(expectedOutputs[0].text)) + { + Assert.True(false, GenerateExpectedHostOutput(result)); + } + else + { + var hostOutputs = result.GetHostOutputs(); + Assert.Equal(expectedOutputs.Length, hostOutputs.Length); + for (int i = 0; i < hostOutputs.Length; i++) + { + var expectedOutput = expectedOutputs[i]; + var actualOutput = hostOutputs[i]; + + Assert.Equal(expectedOutput.hintName, actualOutput.Key); + Assert.Equal(expectedOutput.text, actualOutput.Value, ignoreWhiteSpaceDifferences: true); + } + } + + return result; + } + private static string CreateBaselineDirectory(string testPath, string testName) { var baselineDirectory = Path.Join( @@ -490,9 +515,9 @@ private static void DeleteUnusedBaselines(string baselineDirectory, HashSet 0) @@ -504,6 +529,22 @@ private static string GenerateExpectedOutput(GeneratorRunResult result) return sb.ToString(); } + private static string GenerateExpectedHostOutput(GeneratorRunResult result) + { + StringBuilder sb = new StringBuilder("Generated Host Output:").AppendLine().AppendLine(); + var hostOutputs = result.GetHostOutputs(); + for (int i = 0; i < hostOutputs.Length; i++) + { + if (i > 0) + { + sb.AppendLine(","); + } + sb.Append("(@\"").Append(hostOutputs[i].Key.Replace("\"", "\"\"")).Append("\", "); + sb.Append("@\"").Append(hostOutputs[i].Value.Replace("\"", "\"\"")).Append("\")"); + } + return sb.ToString(); + } + public static GeneratorRunResult VerifyOutputsMatch(this GeneratorRunResult actual, GeneratorRunResult expected, params (int index, string replacement)[] diffs) { Assert.Equal(actual.GeneratedSources.Length, expected.GeneratedSources.Length); @@ -532,4 +573,19 @@ private static string TrimChecksum(string text) Assert.StartsWith("#pragma", trimmed); return trimmed.Substring(trimmed.IndexOf('\n') + 1); } + + public static void AssertSingleItem(this RazorEventListener.RazorEvent e, string expectedEventName, string expectedFileName) + { + Assert.Equal(expectedEventName, e.EventName); + var file = Assert.Single(e.Payload); + Assert.Equal(expectedFileName, file); + } + + public static void AssertPair(this RazorEventListener.RazorEvent e, string expectedEventName, string payload1, string payload2) + { + Assert.Equal(expectedEventName, e.EventName); + Assert.Equal(2, e.Payload.Length); + Assert.Equal(payload1, e.Payload[0]); + Assert.Equal(payload2, e.Payload[1]); + } }