diff --git a/sdk.sln b/sdk.sln
index a5cea10f20a5..95184d771db8 100644
--- a/sdk.sln
+++ b/sdk.sln
@@ -264,15 +264,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.WorkloadM
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuiltInTools", "BuiltInTools", "{71A9F549-0EB6-41F9-BC16-4A6C5007FC91}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-watch", "src\BuiltInTools\dotnet-watch\dotnet-watch.csproj", "{445EFBD5-6730-4F09-943D-278E77501FFD}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch", "src\BuiltInTools\dotnet-watch\dotnet-watch.csproj", "{445EFBD5-6730-4F09-943D-278E77501FFD}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Watch.BrowserRefresh", "src\BuiltInTools\BrowserRefresh\Microsoft.AspNetCore.Watch.BrowserRefresh.csproj", "{A82EF2B9-24BC-4569-8FE5-9EF51017F4CB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Watch.BrowserRefresh", "src\BuiltInTools\BrowserRefresh\Microsoft.AspNetCore.Watch.BrowserRefresh.csproj", "{A82EF2B9-24BC-4569-8FE5-9EF51017F4CB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-watch.Tests", "src\Tests\dotnet-watch.Tests\dotnet-watch.Tests.csproj", "{CCE1A328-9CFE-44D3-B68F-FE84A039ACEA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch.Tests", "src\Tests\dotnet-watch.Tests\dotnet-watch.Tests.csproj", "{CCE1A328-9CFE-44D3-B68F-FE84A039ACEA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Watch.BrowserRefresh.Tests", "src\Tests\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj", "{81ADA3FA-AC26-4149-8CFC-EC7808ECB820}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Watch.BrowserRefresh.Tests", "src\Tests\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj", "{81ADA3FA-AC26-4149-8CFC-EC7808ECB820}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetWatchTasks", "src\BuiltInTools\DotNetWatchTasks\DotNetWatchTasks.csproj", "{A41DF752-6F21-4036-AD02-DD37B11A2723}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWatchTasks", "src\BuiltInTools\DotNetWatchTasks\DotNetWatchTasks.csproj", "{A41DF752-6F21-4036-AD02-DD37B11A2723}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.BlazorWebAssembly.Tasks", "src\BlazorWasmSdk\Tasks\Microsoft.NET.Sdk.BlazorWebAssembly.Tasks.csproj", "{4AE60971-C960-4DDF-B671-8B3E32C1AFD2}"
EndProject
@@ -330,6 +330,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rules", "Rules", "{A117DF29
src\RazorSdk\Targets\Rules\RazorGenerateWithTargetPath.xaml = src\RazorSdk\Targets\Rules\RazorGenerateWithTargetPath.xaml
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceGenerators", "{C2B15A41-A9C0-456A-A9FF-649E9237D850}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Razor.SourceGenerators", "src\RazorSdk\SourceGenerators\Microsoft.NET.Sdk.Razor.SourceGenerators.csproj", "{56C34654-DE8F-4F14-B2F8-6C37285B786E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -588,6 +592,10 @@ Global
{08C9E634-39F3-4B24-BCEA-D0B21971EBBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08C9E634-39F3-4B24-BCEA-D0B21971EBBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08C9E634-39F3-4B24-BCEA-D0B21971EBBE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {56C34654-DE8F-4F14-B2F8-6C37285B786E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {56C34654-DE8F-4F14-B2F8-6C37285B786E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {56C34654-DE8F-4F14-B2F8-6C37285B786E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {56C34654-DE8F-4F14-B2F8-6C37285B786E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -696,6 +704,8 @@ Global
{4ACCAC39-8ECD-45F8-B5AF-EB7E37CD36CC} = {E9BDA8A6-1AD2-4D0E-A37C-9EC10D7FE773}
{D64884FD-A3F3-4082-A814-A9377B661509} = {E9BDA8A6-1AD2-4D0E-A37C-9EC10D7FE773}
{A117DF29-1D72-453B-A24C-3B53F47609F2} = {D64884FD-A3F3-4082-A814-A9377B661509}
+ {C2B15A41-A9C0-456A-A9FF-649E9237D850} = {E9BDA8A6-1AD2-4D0E-A37C-9EC10D7FE773}
+ {56C34654-DE8F-4F14-B2F8-6C37285B786E} = {C2B15A41-A9C0-456A-A9FF-649E9237D850}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FB8F26CE-4DE6-433F-B32A-79183020BBD6}
diff --git a/src/Assets/TestProjects/RazorMvcWithComponents/MvcWithComponents.csproj b/src/Assets/TestProjects/RazorMvcWithComponents/MvcWithComponents.csproj
index fac9f14f4247..182c0c3521c5 100644
--- a/src/Assets/TestProjects/RazorMvcWithComponents/MvcWithComponents.csproj
+++ b/src/Assets/TestProjects/RazorMvcWithComponents/MvcWithComponents.csproj
@@ -9,8 +9,4 @@
false
-
-
-
-
diff --git a/src/Assets/TestProjects/RazorMvcWithComponents/Views/Home/#FileName.cshtml b/src/Assets/TestProjects/RazorMvcWithComponents/Views/Home/FileName.cshtml
similarity index 100%
rename from src/Assets/TestProjects/RazorMvcWithComponents/Views/Home/#FileName.cshtml
rename to src/Assets/TestProjects/RazorMvcWithComponents/Views/Home/FileName.cshtml
diff --git a/src/Assets/TestProjects/RazorSimpleMvc50/SimpleMvc50.csproj b/src/Assets/TestProjects/RazorSimpleMvc50/SimpleMvc50.csproj
index c526d91447ff..10508e09d6c8 100644
--- a/src/Assets/TestProjects/RazorSimpleMvc50/SimpleMvc50.csproj
+++ b/src/Assets/TestProjects/RazorSimpleMvc50/SimpleMvc50.csproj
@@ -1,7 +1,7 @@
- $(AspNetTestTfm)
+ net5.0
diff --git a/src/Layout/redist/targets/GenerateLayout.targets b/src/Layout/redist/targets/GenerateLayout.targets
index 0fd35b2a2654..55aaf4ddf883 100644
--- a/src/Layout/redist/targets/GenerateLayout.targets
+++ b/src/Layout/redist/targets/GenerateLayout.targets
@@ -236,22 +236,31 @@
Targets="Publish"
Projects="$(RepoRoot)/src/RazorSdk/Tool/Microsoft.NET.Sdk.Razor.Tool.csproj"
Properties="Configuration=$(Configuration)" />
+
<_RazorToolOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor.Tool\$(Configuration)\$(SdkTargetFramework)\publish\*.*" />
<_MvcRazorExtensionOutput Include="$(ArtifactsBinDir)$(Configuration)\Sdks\Microsoft.NET.Sdk.Razor\tasks\$(SdkTargetFramework)\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll" />
+ <_RazorSourceGeneratorsOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor.SourceGenerators\$(Configuration)\netstandard2.0\publish\*.*" />
-
+
_action;
+
+ public ConfigureRazorCodeGenerationOptions(Action action)
+ {
+ _action = action;
+ }
+
+ public int Order { get; set; }
+
+ public void Configure(RazorCodeGenerationOptionsBuilder options) => _action(options);
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.csproj b/src/RazorSdk/SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.csproj
new file mode 100644
index 000000000000..37cb96e592a0
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.csproj
@@ -0,0 +1,41 @@
+
+
+
+ netstandard2.0
+ MicrosoftAspNetCore
+
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(GetTargetPathDependsOn);GetDependencyTargetPaths
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RazorSdk/SourceGenerators/RazorDiagnostics.cs b/src/RazorSdk/SourceGenerators/RazorDiagnostics.cs
new file mode 100644
index 000000000000..61c92e07f2d8
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/RazorDiagnostics.cs
@@ -0,0 +1,50 @@
+// 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.Globalization;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal static class RazorDiagnostics
+ {
+ public static readonly DiagnosticDescriptor InvalidRazorLangVersionDescriptor = new DiagnosticDescriptor(
+#pragma warning disable RS2008 // Enable analyzer release tracking
+ "RZ3600",
+#pragma warning restore RS2008 // Enable analyzer release tracking
+ "Invalid RazorLangVersion",
+ "Invalid value {0} for RazorLangVersion. Valid values include 'Latest' or a valid version in range 1.0 to 5.0.",
+ "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static Diagnostic AsDiagnostic(this RazorDiagnostic razorDiagnostic)
+ {
+ var descriptor = new DiagnosticDescriptor(
+ razorDiagnostic.Id,
+ razorDiagnostic.GetMessage(CultureInfo.CurrentCulture),
+ razorDiagnostic.GetMessage(CultureInfo.CurrentCulture),
+ "Razor",
+ razorDiagnostic.Severity switch
+ {
+ RazorDiagnosticSeverity.Error => DiagnosticSeverity.Error,
+ RazorDiagnosticSeverity.Warning => DiagnosticSeverity.Warning,
+ _ => DiagnosticSeverity.Hidden,
+ },
+ isEnabledByDefault: true);
+
+ var span = razorDiagnostic.Span;
+ var location = Location.Create(
+ span.FilePath,
+ span.AsTextSpan(),
+ new LinePositionSpan(
+ new LinePosition(span.LineIndex, span.CharacterIndex),
+ new LinePosition(span.LineIndex, span.CharacterIndex + span.Length)));
+
+ return Diagnostic.Create(descriptor, location);
+ }
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/RazorInputItem.cs b/src/RazorSdk/SourceGenerators/RazorInputItem.cs
new file mode 100644
index 000000000000..d7c109f612a0
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/RazorInputItem.cs
@@ -0,0 +1,39 @@
+// 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.IO;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal readonly struct RazorInputItem
+ {
+ public RazorInputItem(AdditionalText additionalText, string relativePath, string fileKind, string generatedOutputPath, string generatedDeclarationPath, string cssScope)
+ {
+ AdditionalText = additionalText;
+ RelativePath = relativePath;
+ CssScope = cssScope;
+ NormalizedPath = '/' + relativePath
+ .Replace(Path.DirectorySeparatorChar, '/')
+ .Replace("//", "/");
+ FileKind = fileKind;
+ GeneratedOutputPath = generatedOutputPath;
+ GeneratedDeclarationPath = generatedDeclarationPath;
+ }
+
+ public AdditionalText AdditionalText { get; }
+
+ public string RelativePath { get; }
+
+ public string NormalizedPath { get; }
+
+ public string FileKind { get; }
+
+ public string CssScope { get; }
+
+ public string GeneratedOutputPath { get; }
+
+ public string GeneratedDeclarationPath { get; }
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/RazorSourceGenerationContext.cs b/src/RazorSdk/SourceGenerators/RazorSourceGenerationContext.cs
new file mode 100644
index 000000000000..9e8e064503ac
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/RazorSourceGenerationContext.cs
@@ -0,0 +1,197 @@
+// 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.Diagnostics;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal class RazorSourceGenerationContext
+ {
+ public string RootNamespace { get; private set; }
+
+ public IReadOnlyList RazorFiles { get; private set; }
+
+ public IReadOnlyList CshtmlFiles { get; private set; }
+
+ public VirtualRazorProjectFileSystem FileSystem { get; private set; }
+
+ public RazorConfiguration Configuration { get; private set; }
+
+ public bool DesignTimeBuild { get; private set; }
+
+ public string RefsTagHelperOutputCachePath { get; private set; }
+
+ ///
+ /// Gets a flag that determines if the source generator waits for the debugger to attach.
+ ///
+ /// To configure this using MSBuild, use the _RazorSourceGeneratorDebug property.
+ /// For instance dotnet msbuild /p:_RazorSourceGeneratorDebug=true
+ ///
+ ///
+ public bool WaitForDebugger { get; private set; }
+
+ ///
+ /// Gets a flag that determines if generated output is to be written to disk.
+ /// Primarily meant for tests and debugging.
+ ///
+ /// To configure this using MSBuild, use the _RazorSourceGeneratorWriteGeneratedOutput property.
+ /// For instance dotnet msbuild /p:_RazorSourceGeneratorWriteGeneratedOutput=true
+ ///
+ ///
+ public bool WriteGeneratedContent { get; private set; }
+
+ ///
+ /// Gets a flag that determine if the source generator should log verbose messages.
+ ///
+ ///
+ /// To configure this using MSBuild, use the _RazorSourceGeneratorLog property.
+ /// For instance dotnet msbuild /p:_RazorSourceGeneratorLog=true
+ ///
+ public bool EnableLogging { get; private set; }
+
+ public static RazorSourceGenerationContext Create(GeneratorExecutionContext context)
+ {
+ var globalOptions = context.AnalyzerConfigOptions.GlobalOptions;
+
+ if (!globalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace))
+ {
+ rootNamespace = "ASP";
+ }
+
+ globalOptions.TryGetValue("build_property.DesignTimeBuild", out var designTimeBuild);
+ if (!globalOptions.TryGetValue("build_property._RazorReferenceAssemblyTagHelpersOutputPath", out var refsTagHelperOutputCachePath))
+ {
+ throw new InvalidOperationException("_RazorReferenceAssemblyTagHelpersOutputPath is not specified.");
+ }
+
+ if (!globalOptions.TryGetValue("build_property.RazorLangVersion", out var razorLanguageVersionString) ||
+ !RazorLanguageVersion.TryParse(razorLanguageVersionString, out var razorLanguageVersion))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ RazorDiagnostics.InvalidRazorLangVersionDescriptor,
+ Location.None,
+ razorLanguageVersionString));
+
+ return null;
+ }
+
+ if (!globalOptions.TryGetValue("build_property.RazorConfiguration", out var configurationName))
+ {
+ configurationName = "default";
+ }
+
+ globalOptions.TryGetValue("build_property._RazorSourceGeneratorDebug", out var waitForDebugger);
+ globalOptions.TryGetValue("build_property._RazorSourceGeneratorWriteGeneratedOutput", out var writeOutput);
+ globalOptions.TryGetValue("build_property._RazorSourceGeneratorLog", out var enableLogging);
+
+ var razorConfiguration = RazorConfiguration.Create(razorLanguageVersion, configurationName, Enumerable.Empty());
+ var (razorFiles, cshtmlFiles) = GetRazorInputs(context);
+ var fileSystem = GetVirtualFileSystem(razorFiles, cshtmlFiles);
+
+ return new RazorSourceGenerationContext
+ {
+ RootNamespace = rootNamespace,
+ Configuration = razorConfiguration,
+ FileSystem = fileSystem,
+ RazorFiles = razorFiles,
+ CshtmlFiles = cshtmlFiles,
+ DesignTimeBuild = designTimeBuild == "true",
+ RefsTagHelperOutputCachePath = refsTagHelperOutputCachePath,
+ WaitForDebugger = waitForDebugger == "true",
+ WriteGeneratedContent = writeOutput == "true",
+ EnableLogging = enableLogging == "true"
+ };
+ }
+
+ private static VirtualRazorProjectFileSystem GetVirtualFileSystem(IReadOnlyList razorFiles, IReadOnlyList cshtmlFiles)
+ {
+ var fileSystem = new VirtualRazorProjectFileSystem();
+ for (var i = 0; i < razorFiles.Count; i++)
+ {
+ var item = razorFiles[i];
+ fileSystem.Add(new SourceGeneratorProjectItem(
+ basePath: "/",
+ filePath: item.NormalizedPath,
+ relativePhysicalPath: item.RelativePath,
+ fileKind: FileKinds.Component,
+ item.AdditionalText,
+ cssScope: item.CssScope));
+ }
+
+ for (var i = 0; i < cshtmlFiles.Count; i++)
+ {
+ var item = cshtmlFiles[i];
+ fileSystem.Add(new SourceGeneratorProjectItem(
+ basePath: "/",
+ filePath: item.NormalizedPath,
+ relativePhysicalPath: item.RelativePath,
+ fileKind: FileKinds.Legacy,
+ item.AdditionalText,
+ cssScope: item.CssScope));
+ }
+
+ return fileSystem;
+ }
+
+ private static (IReadOnlyList razorFiles, IReadOnlyList cshtmlFiles) GetRazorInputs(GeneratorExecutionContext context)
+ {
+ List razorFiles = null;
+ List cshtmlFiles = null;
+
+ foreach (var item in context.AdditionalFiles)
+ {
+ var path = item.Path;
+ var isComponent = path.EndsWith(".razor", StringComparison.OrdinalIgnoreCase);
+ var isRazorView = path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase);
+
+ if (!isComponent && !isRazorView)
+ {
+ continue;
+ }
+
+ var options = context.AnalyzerConfigOptions.GetOptions(item);
+ if (!options.TryGetValue("build_metadata.AdditionalFiles.TargetPath", out var relativePath))
+ {
+ throw new InvalidOperationException($"TargetPath is not specified for additional file '{item.Path}.");
+ }
+
+ options.TryGetValue("build_metadata.AdditionalFiles.CssScope", out var cssScope);
+
+ string generatedDeclarationPath = null;
+
+ options.TryGetValue("build_metadata.AdditionalFiles.GeneratedOutputFullPath", out var generatedOutputPath);
+ if (isComponent)
+ {
+ options.TryGetValue("build_metadata.AdditionalFiles.GeneratedDeclarationFullPath", out generatedDeclarationPath);
+ }
+
+ var fileKind = isComponent ? FileKinds.GetComponentFileKindFromFilePath(item.Path) : FileKinds.Legacy;
+
+ var inputItem = new RazorInputItem(item, relativePath, fileKind, generatedOutputPath, generatedDeclarationPath, cssScope);
+
+ if (isComponent)
+ {
+ razorFiles ??= new();
+ razorFiles.Add(inputItem);
+ }
+ else
+ {
+ cshtmlFiles ??= new();
+ cshtmlFiles.Add(inputItem);
+ }
+ }
+
+ return (
+ (IReadOnlyList)razorFiles ?? Array.Empty(),
+ (IReadOnlyList)cshtmlFiles ?? Array.Empty()
+ );
+ }
+
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/RazorSourceGenerator.cs b/src/RazorSdk/SourceGenerators/RazorSourceGenerator.cs
new file mode 100644
index 000000000000..06c67440db84
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/RazorSourceGenerator.cs
@@ -0,0 +1,280 @@
+// 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.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc.Razor.Extensions;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ [Generator]
+ public partial class RazorSourceGenerator : ISourceGenerator
+ {
+ private static readonly ParallelOptions DefaultParallelOptions = new();
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var razorContext = RazorSourceGenerationContext.Create(context);
+ if (razorContext is null ||
+ (razorContext.RazorFiles.Count == 0 && razorContext.CshtmlFiles.Count == 0))
+ {
+ return;
+ }
+
+ HandleDebugSwitch(razorContext.WaitForDebugger);
+
+ var tagHelpers = ResolveTagHelperDescriptors(context, razorContext);
+
+ var projectEngine = RazorProjectEngine.Create(razorContext.Configuration, razorContext.FileSystem, b =>
+ {
+ b.Features.Add(new DefaultTypeNameFeature());
+ b.SetRootNamespace(razorContext.RootNamespace);
+
+ b.Features.Add(new StaticTagHelperFeature { TagHelpers = tagHelpers, });
+ b.Features.Add(new DefaultTagHelperDescriptorProvider());
+
+ CompilerFeatures.Register(b);
+ RazorExtensions.Register(b);
+
+ b.SetCSharpLanguageVersion(((CSharpParseOptions)context.ParseOptions).LanguageVersion);
+ });
+
+ CodeGenerateRazorComponents(context, razorContext, projectEngine);
+ GenerateViews(context, razorContext, projectEngine);
+ }
+
+ private void GenerateViews(GeneratorExecutionContext context, RazorSourceGenerationContext razorContext, RazorProjectEngine projectEngine)
+ {
+ var files = razorContext.CshtmlFiles;
+
+ Parallel.For(0, files.Count, GetParallelOptions(), i =>
+ {
+ var file = files[i];
+
+ var codeDocument = projectEngine.Process(projectEngine.FileSystem.GetItem(file.NormalizedPath, FileKinds.Legacy));
+ var csharpDocument = codeDocument.GetCSharpDocument();
+ for (var j = 0; j < csharpDocument.Diagnostics.Count; j++)
+ {
+ var razorDiagnostic = csharpDocument.Diagnostics[j];
+ var csharpDiagnostic = razorDiagnostic.AsDiagnostic();
+ context.ReportDiagnostic(csharpDiagnostic);
+ }
+
+ Directory.CreateDirectory(Path.GetDirectoryName(file.GeneratedOutputPath));
+ File.WriteAllText(file.GeneratedOutputPath, csharpDocument.GeneratedCode);
+ });
+ }
+
+ private static void CodeGenerateRazorComponents(GeneratorExecutionContext context, RazorSourceGenerationContext razorContext, RazorProjectEngine projectEngine)
+ {
+ var files = razorContext.RazorFiles;
+
+ var arraypool = ArrayPool<(string, SourceText)>.Shared;
+ var outputs = arraypool.Rent(files.Count);
+
+ Parallel.For(0, files.Count, GetParallelOptions(), i =>
+ {
+ var file = files[i];
+ var projectItem = projectEngine.FileSystem.GetItem(file.NormalizedPath, FileKinds.Component);
+
+ var codeDocument = projectEngine.Process(projectItem);
+ var csharpDocument = codeDocument.GetCSharpDocument();
+ for (var j = 0; j < csharpDocument.Diagnostics.Count; j++)
+ {
+ var razorDiagnostic = csharpDocument.Diagnostics[j];
+ var csharpDiagnostic = razorDiagnostic.AsDiagnostic();
+ context.ReportDiagnostic(csharpDiagnostic);
+ }
+
+ var hint = GetIdentifierFromPath(file.NormalizedPath);
+
+ var generatedCode = csharpDocument.GeneratedCode;
+ if (razorContext.WriteGeneratedContent)
+ {
+ var path = file.GeneratedOutputPath;
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ File.WriteAllText(path, generatedCode);
+ }
+
+ outputs[i] = (hint, SourceText.From(generatedCode, Encoding.UTF8));
+ });
+
+ for (var i = 0; i < files.Count; i++)
+ {
+ var (hint, sourceText) = outputs[i];
+ context.AddSource(hint, sourceText);
+ }
+
+ arraypool.Return(outputs);
+ }
+
+ private static IReadOnlyList ResolveTagHelperDescriptors(GeneratorExecutionContext GeneratorExecutionContext, RazorSourceGenerationContext razorContext)
+ {
+ var tagHelperFeature = new StaticCompilationTagHelperFeature();
+
+ var langVersion = ((CSharpParseOptions)GeneratorExecutionContext.ParseOptions).LanguageVersion;
+
+ var discoveryProjectEngine = RazorProjectEngine.Create(razorContext.Configuration, razorContext.FileSystem, b =>
+ {
+ b.Features.Add(new DefaultTypeNameFeature());
+ b.Features.Add(new ConfigureRazorCodeGenerationOptions(options =>
+ {
+ options.SuppressPrimaryMethodBody = true;
+ options.SuppressChecksum = true;
+ }));
+
+ b.SetRootNamespace(razorContext.RootNamespace);
+
+ var metadataReferences = new List(GeneratorExecutionContext.Compilation.References);
+ b.Features.Add(new DefaultMetadataReferenceFeature { References = metadataReferences });
+
+ b.Features.Add(tagHelperFeature);
+ b.Features.Add(new DefaultTagHelperDescriptorProvider());
+
+ CompilerFeatures.Register(b);
+ RazorExtensions.Register(b);
+
+ b.SetCSharpLanguageVersion(langVersion);
+ });
+
+ var files = razorContext.RazorFiles;
+ var results = ArrayPool.Shared.Rent(files.Count);
+
+ var parseOptions = (CSharpParseOptions)GeneratorExecutionContext.ParseOptions;
+
+ Parallel.For(0, files.Count, GetParallelOptions(), i =>
+ {
+ var file = files[i];
+ if (File.GetLastWriteTimeUtc(file.GeneratedDeclarationPath) > File.GetLastWriteTimeUtc(file.AdditionalText.Path))
+ {
+ // Declaration files are invariant to other razor files, tag helpers, assemblies. If we have previously generated
+ // content that it's still newer than the output file, use it and save time processing the file.
+ using var outputFileStream = File.OpenRead(file.GeneratedDeclarationPath);
+ results[i] = CSharpSyntaxTree.ParseText(
+ SourceText.From(outputFileStream),
+ options: parseOptions);
+ }
+ else
+ {
+ var codeGen = discoveryProjectEngine.Process(discoveryProjectEngine.FileSystem.GetItem(file.NormalizedPath, FileKinds.Component));
+ var generatedCode = codeGen.GetCSharpDocument().GeneratedCode;
+
+ Directory.CreateDirectory(Path.GetDirectoryName(file.GeneratedDeclarationPath));
+ File.WriteAllText(file.GeneratedDeclarationPath, generatedCode);
+
+ results[i] = CSharpSyntaxTree.ParseText(
+ generatedCode,
+ options: parseOptions);
+ }
+ });
+
+ tagHelperFeature.Compilation = GeneratorExecutionContext.Compilation.AddSyntaxTrees(results.Take(files.Count));
+ ArrayPool.Shared.Return(results);
+
+ var lastUpdatedReferenceUtc = GetLastUpdatedReference(GeneratorExecutionContext.Compilation.References);
+ IReadOnlyList refTagHelpers;
+
+ if (lastUpdatedReferenceUtc is not null && lastUpdatedReferenceUtc < File.GetLastWriteTimeUtc(razorContext.RefsTagHelperOutputCachePath))
+ {
+ // Producing tag helpers from a Compilation every time is surprisingly expensive. So we'll use some caching strategies to mitigate this until
+ // we can improve the perf in that area.
+
+ // TagHelpers can come from two locations - the declaration files
+ // and the assemblies participating in the compilation.
+ // In a typical inner loop, the assemblies referenced by the project do not change. We could cache these separately from the tag helpers produced
+ // by the app to avoid some per-compilation costs.
+ // We determine if any of the reference assemblies have a newer timestamp than the output cache for the tag helpers. If not, we can re-use previously
+ // calculated results.
+ refTagHelpers = TagHelperSerializer.Deserialize(razorContext.RefsTagHelperOutputCachePath);
+ }
+ else
+ {
+ tagHelperFeature.DiscoveryMode = TagHelperDiscoveryFilter.ReferenceAssemblies;
+ refTagHelpers = tagHelperFeature.GetDescriptors();
+ TagHelperSerializer.Serialize(razorContext.RefsTagHelperOutputCachePath, refTagHelpers);
+ }
+
+ tagHelperFeature.DiscoveryMode = TagHelperDiscoveryFilter.CurrentCompilation;
+ var assemblyTagHelpers = tagHelperFeature.GetDescriptors();
+
+ var result = new List(refTagHelpers.Count + assemblyTagHelpers.Count);
+ result.AddRange(assemblyTagHelpers);
+ result.AddRange(refTagHelpers);
+
+ return result;
+ }
+
+ private static DateTime? GetLastUpdatedReference(IEnumerable references)
+ {
+ DateTime lastWriteTimeUtc = DateTime.MinValue;
+
+ foreach (var reference in references)
+ {
+ // We expect all references in the compilation context to be backed by a file on disk. If not,
+ // we'll bail out and regenerate the tag helper cache where this is invoked.
+ if (reference is not PortableExecutableReference portableExecutableReference || string.IsNullOrEmpty(portableExecutableReference.FilePath))
+ {
+ return null;
+ }
+
+ var fileWriteTime = File.GetLastWriteTimeUtc(portableExecutableReference.FilePath);
+
+ lastWriteTimeUtc = lastWriteTimeUtc < fileWriteTime ? fileWriteTime : lastWriteTimeUtc;
+ }
+
+ return lastWriteTimeUtc;
+ }
+
+ private static string GetIdentifierFromPath(string filePath)
+ {
+ var builder = new StringBuilder(filePath.Length);
+
+ for (var i = 0; i < filePath.Length; i++)
+ {
+ builder.Append(filePath[i] switch
+ {
+ ':' or '\\' or '/' => '_',
+ var @default => @default,
+ });
+ }
+
+ return builder.ToString();
+ }
+
+ private static ParallelOptions GetParallelOptions()
+ {
+ if (Debugger.IsAttached)
+ {
+ return new ParallelOptions { MaxDegreeOfParallelism = 1 };
+ }
+ return DefaultParallelOptions;
+ }
+
+ private static void HandleDebugSwitch(bool waitForDebugger)
+ {
+ if (waitForDebugger)
+ {
+ while (!Debugger.IsAttached)
+ {
+ Thread.Sleep(3000);
+ }
+ }
+ }
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/SourceGeneratorProjectItem.cs b/src/RazorSdk/SourceGenerators/SourceGeneratorProjectItem.cs
new file mode 100644
index 000000000000..947caf5639a5
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/SourceGeneratorProjectItem.cs
@@ -0,0 +1,48 @@
+// 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.Text;
+using System.IO;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal class SourceGeneratorProjectItem : RazorProjectItem
+ {
+ private readonly string _fileKind;
+
+ public SourceGeneratorProjectItem(string basePath, string filePath, string relativePhysicalPath, string fileKind, AdditionalText additionalText, string cssScope)
+ {
+ BasePath = basePath;
+ FilePath = filePath;
+ RelativePhysicalPath = relativePhysicalPath;
+ _fileKind = fileKind;
+ AdditionalText = additionalText;
+ CssScope = cssScope;
+ var text = AdditionalText.GetText();
+ RazorSourceDocument = new SourceTextRazorSourceDocument(AdditionalText.Path, relativePhysicalPath, text);
+ }
+
+ public AdditionalText AdditionalText { get; }
+
+ public override string BasePath { get; }
+
+ public override string FilePath { get; }
+
+ public override bool Exists => true;
+
+ public override string PhysicalPath => AdditionalText.Path;
+
+ public override string RelativePhysicalPath { get; }
+
+ public override string FileKind => _fileKind ?? base.FileKind;
+
+ public override string CssScope { get; }
+
+ public override Stream Read()
+ => throw new NotSupportedException("This API should not be invoked. We should instead be relying on " +
+ "the RazorSourceDocument associated with this item instead.");
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/SourceTextRazorSourceDocument.cs b/src/RazorSdk/SourceGenerators/SourceTextRazorSourceDocument.cs
new file mode 100644
index 000000000000..7b1b12e57d64
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/SourceTextRazorSourceDocument.cs
@@ -0,0 +1,70 @@
+// 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.Linq;
+using System.Text;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal class SourceTextRazorSourceDocument : RazorSourceDocument
+ {
+ private readonly SourceText _sourceText;
+
+ public SourceTextRazorSourceDocument(string filePath, string relativePath, SourceText sourceText)
+ {
+ FilePath = filePath;
+ RelativePath = relativePath;
+ _sourceText = sourceText;
+ Lines = new SourceTextSourceLineCollection(filePath, sourceText.Lines);
+ }
+
+ public override char this[int position] => _sourceText[position];
+
+ public override Encoding Encoding => _sourceText.Encoding;
+
+ public override string FilePath { get; }
+
+ public override int Length => _sourceText.Length;
+
+ public override string RelativePath { get; }
+
+ public override RazorSourceLineCollection Lines { get; }
+
+ public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
+ {
+ _sourceText.CopyTo(sourceIndex, destination, destinationIndex, count);
+ }
+
+ public override byte[] GetChecksum() => _sourceText.GetChecksum().ToArray();
+
+ public override string GetChecksumAlgorithm() => _sourceText.ChecksumAlgorithm.ToString().ToUpperInvariant();
+
+ private class SourceTextSourceLineCollection : RazorSourceLineCollection
+ {
+ private readonly string _filePath;
+ private readonly TextLineCollection _textLines;
+
+ public SourceTextSourceLineCollection(string filePath, TextLineCollection textLines)
+ {
+ _filePath = filePath;
+ _textLines = textLines;
+ }
+
+ public override int Count => _textLines.Count;
+
+ public override int GetLineLength(int index)
+ {
+ var line = _textLines[index];
+ return line.EndIncludingLineBreak - line.Start;
+ }
+
+ internal override SourceLocation GetLocation(int position)
+ {
+ var line = _textLines.GetLineFromPosition(position);
+ return new SourceLocation(_filePath, position, line.LineNumber, position);
+ }
+ }
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/StaticCompilationTagHelperFeature.cs b/src/RazorSdk/SourceGenerators/StaticCompilationTagHelperFeature.cs
new file mode 100644
index 000000000000..ef683579f33a
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/StaticCompilationTagHelperFeature.cs
@@ -0,0 +1,56 @@
+// 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.Linq;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal sealed class StaticCompilationTagHelperFeature : RazorEngineFeatureBase, ITagHelperFeature
+ {
+ private ITagHelperDescriptorProvider[] _providers;
+
+ public IReadOnlyList GetDescriptors()
+ {
+ if (Compilation is null)
+ {
+ return Array.Empty();
+ }
+
+ var results = new List();
+
+
+ var context = TagHelperDescriptorProviderContext.Create(results);
+ context.SetCompilation(Compilation);
+
+ for (var i = 0; i < _providers.Length; i++)
+ {
+ _providers[i].Execute(context);
+ }
+
+ return results;
+ }
+
+ public Compilation Compilation { get; set; }
+
+ public TagHelperDiscoveryFilter DiscoveryMode { get; set; }
+
+ protected override void OnInitialized()
+ {
+ _providers = Engine.Features.OfType().OrderBy(f => f.Order).ToArray();
+ }
+
+ internal static bool IsValidCompilation(Compilation compilation)
+ {
+ var @string = compilation.GetSpecialType(SpecialType.System_String);
+
+ // Do some minimal tests to verify the compilation is valid. If symbols for System.String
+ // is missing or errored, the compilation may be missing references.
+ return @string != null && @string.TypeKind != TypeKind.Error;
+ }
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/StaticTagHelperFeature.cs b/src/RazorSdk/SourceGenerators/StaticTagHelperFeature.cs
new file mode 100644
index 000000000000..19eede74f63f
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/StaticTagHelperFeature.cs
@@ -0,0 +1,17 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal sealed class StaticTagHelperFeature : ITagHelperFeature
+ {
+ public RazorEngine Engine { get; set; }
+
+ public IReadOnlyList TagHelpers { get; set; }
+
+ public IReadOnlyList GetDescriptors() => TagHelpers;
+ }
+}
diff --git a/src/RazorSdk/SourceGenerators/TagHelperSerializer.cs b/src/RazorSdk/SourceGenerators/TagHelperSerializer.cs
new file mode 100644
index 000000000000..8ee27a906693
--- /dev/null
+++ b/src/RazorSdk/SourceGenerators/TagHelperSerializer.cs
@@ -0,0 +1,40 @@
+// 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;
+using System.IO;
+using System.Text;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor.Serialization;
+using Newtonsoft.Json;
+
+namespace Microsoft.NET.Sdk.Razor.SourceGenerators
+{
+ internal class TagHelperSerializer
+ {
+ private static readonly JsonSerializer Serializer = new JsonSerializer
+ {
+ Converters =
+ {
+ new TagHelperDescriptorJsonConverter(),
+ new RazorDiagnosticJsonConverter(),
+ }
+ };
+
+ public static void Serialize(string manifestFilePath, IReadOnlyList tagHelpers)
+ {
+ using var stream = File.OpenWrite(manifestFilePath);
+ using var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 4096, leaveOpen: true);
+
+ Serializer.Serialize(writer, tagHelpers);
+ }
+
+ public static IReadOnlyList Deserialize(string manifestFilePath)
+ {
+ using var stream = File.OpenRead(manifestFilePath);
+ using var reader = new JsonTextReader(new StreamReader(stream));
+
+ return Serializer.Deserialize>(reader);
+ }
+ }
+}
diff --git a/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.CodeGeneration.targets b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.CodeGeneration.targets
index 76c4bba7c972..e06d9b9d58ff 100644
--- a/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.CodeGeneration.targets
+++ b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.CodeGeneration.targets
@@ -171,15 +171,7 @@ Copyright (c) .NET Foundation. All rights reserved.
- $(ResolveRazorCompileInputsDependsOn);_ResolveGeneratedRazorCompileInputs
+ $(ResolveRazorCompileInputsDependsOn)
-
-
-
-
-
-
diff --git a/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Compilation.targets b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Compilation.targets
index c512c7ee49f2..fd86f896df7f 100644
--- a/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Compilation.targets
+++ b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Compilation.targets
@@ -66,8 +66,10 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+
+
+
true
@@ -156,7 +164,7 @@ Copyright (c) .NET Foundation. All rights reserved.
Prefer32Bit="$(Prefer32Bit)"
PreferredUILang="$(PreferredUILang)"
ProvideCommandLineArgs="$(ProvideCommandLineArgs)"
- References="@(RazorReferencePath)"
+ References="@(ReferencePath);@(IntermediateAssembly)"
ReportAnalyzer="$(ReportAnalyzer)"
Resources="@(_RazorCoreCompileResourceInputs);@(CompiledLicenseFile)"
ResponseFiles="$(CompilerResponseFile)"
diff --git a/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.SourceGenerators.targets b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.SourceGenerators.targets
new file mode 100644
index 000000000000..bf0d32a29d8d
--- /dev/null
+++ b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.SourceGenerators.targets
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+ <_RazorSdkSourceGeneratorDirectoryRoot>$(RazorSdkDirectoryRoot)\source-generators\
+ <_RazorReferenceAssemblyTagHelpersOutputPath>$([System.IO.Path]::GetFullPath($(IntermediateOutputPath)))RazorTagHelper.refs.out.cache
+
+
+
+
+ <_RazorAnalyzer Include="$(_RazorSdkSourceGeneratorDirectoryRoot)Newtonsoft.Json.dll" />
+ <_RazorAnalyzer Include="$(_RazorSdkSourceGeneratorDirectoryRoot)Microsoft.AspNetCore.Mvc.Razor.Extensions.dll" />
+ <_RazorAnalyzer Include="$(_RazorSdkSourceGeneratorDirectoryRoot)Microsoft.AspNetCore.Razor.Language.dll" />
+ <_RazorAnalyzer Include="$(_RazorSdkSourceGeneratorDirectoryRoot)Microsoft.CodeAnalysis.Razor.dll" />
+ <_RazorAnalyzer Include="$(_RazorSdkSourceGeneratorDirectoryRoot)Microsoft.NET.Sdk.Razor.SourceGenerators.dll" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_RazorAdditionalFile Include="@(RazorComponentWithTargetPath)" />
+
+ <_RazorAdditionalFile Include="@(RazorGenerateWithTargetPath)" Condition="'$(RazorCompileOnBuild)' != 'false'" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RazorSdk/Targets/Sdk.Razor.CurrentVersion.targets b/src/RazorSdk/Targets/Sdk.Razor.CurrentVersion.targets
index 45410b321bc3..bdface52f55e 100644
--- a/src/RazorSdk/Targets/Sdk.Razor.CurrentVersion.targets
+++ b/src/RazorSdk/Targets/Sdk.Razor.CurrentVersion.targets
@@ -42,19 +42,36 @@ Copyright (c) .NET Foundation. All rights reserved.
<_RazorSdkDotNetHostFileName Condition="'$(OS)' == 'Windows_NT'">dotnet.exe
-
+
+
+
+
+ <_TargetingNETCoreApp30OrLater>true
+ <_TargetingNET50OrLater>true
+ <_TargetingNET60OrLater>true
+ <_UseRazorSourceGenerator>true
+ 5.0
+
+
+
+
+ <_TargetingNETCoreApp30OrLater>true
+ <_TargetingNET50OrLater>true
+ <_UseRazorSourceGenerator>false
+ 5.0
+
+
+
+
+ <_TargetingNETCoreApp30OrLater>true
+ <_UseRazorSourceGenerator>false
+ 3.0
+
+
+
+
+
- <_TargetingNETCoreApp30OrLater Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND
- $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '3.0')) ">true
- <_TargetingNET50OrLater Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND
- $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '5.0')) ">true
-
-
- 5.0
- 3.0
-
-
+
-
+
+
+
@@ -597,17 +624,6 @@ Copyright (c) .NET Foundation. All rights reserved.
-
-
-
-
-
-
-
-
-
+
+
-
+
@@ -879,6 +891,14 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+
+
+
diff --git a/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPwaManifestTests.cs b/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPwaManifestTests.cs
index 07c848fee3f9..12e6a3d49a8f 100644
--- a/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPwaManifestTests.cs
+++ b/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPwaManifestTests.cs
@@ -168,7 +168,6 @@ public void Publish_UpdatesServiceWorkerVersionHash_WhenSourcesChange()
var capture = match.Groups[1].Value;
// Act
- System.Console.WriteLine(testInstance.TestRoot);
var cssFile = Path.Combine(testInstance.TestRoot, "blazorwasm", "LinkToWebRoot", "css", "app.css");
File.WriteAllText(cssFile, ".updated { }");
diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIncrementalismTest.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIncrementalismTest.cs
index 55e79e974237..735b54fd72a2 100644
--- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIncrementalismTest.cs
+++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIncrementalismTest.cs
@@ -72,42 +72,7 @@ public void BuildIncremental_SimpleMvc_PersistsTargetInputFile()
}
}
- [Fact]
- public void RazorGenerate_RegeneratesTagHelperInputs_IfFileChanges()
- {
- var testAsset = "RazorSimpleMvc";
- var projectDirectory = CreateAspNetSdkTestAsset(testAsset);
-
- // Act - 1
- var build = new BuildCommand(projectDirectory);
- var result = build.Execute();
-
- var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
-
- var expectedTagHelperCacheContent = @"""Name"":""SimpleMvc.SimpleTagHelper""";
- var file = Path.Combine(projectDirectory.Path, "SimpleTagHelper.cs");
- var tagHelperOutputCache = Path.Combine(intermediateOutputPath, "SimpleMvc.TagHelpers.output.cache");
- var generatedFile = Path.Combine(intermediateOutputPath, "Razor", "Views", "Home", "Index.cshtml.g.cs");
-
- // Assert - 1
- result.Should().Pass();
- new FileInfo(tagHelperOutputCache).Should().Contain(expectedTagHelperCacheContent);
- var fileThumbPrint = FileThumbPrint.Create(generatedFile);
-
- // Act - 2
- // Update the source content and build. We should expect the outputs to be regenerated.
- File.WriteAllText(file, string.Empty);
- build = new BuildCommand(projectDirectory);
- result = build.Execute();
-
- // Assert - 2
- result.Should().Pass();
- new FileInfo(tagHelperOutputCache).Should().NotContain(@"""Name"":""SimpleMvc.SimpleTagHelper""");
- var newThumbPrint = FileThumbPrint.Create(generatedFile);
- Assert.NotEqual(fileThumbPrint, newThumbPrint);
- }
-
- [Fact]
+ [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/28780")]
public void Build_ErrorInGeneratedCode_ReportsMSBuildError_OnIncrementalBuild()
{
var testAsset = "RazorSimpleMvc";
@@ -175,7 +140,7 @@ void VerifyError(TestAsset projectDirectory)
}
}
- [Fact]
+ [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/28780")]
public void BuildComponents_DoesNotRegenerateComponentDefinition_WhenDefinitionIsUnchanged()
{
var testAsset = "RazorMvcWithComponents";
@@ -232,120 +197,6 @@ public void BuildComponents_DoesNotRegenerateComponentDefinition_WhenDefinitionI
Assert.Equal(definitionThumbprint, FileThumbPrint.Create(tagHelperOutputCache));
}
- [Fact]
- public void BuildComponents_RegeneratesComponentDefinition_WhenFilesChange()
- {
- var testAsset = "RazorMvcWithComponents";
- var projectDirectory = CreateAspNetSdkTestAsset(testAsset);
-
- var build = new BuildCommand(projectDirectory);
-
- var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
- var outputPath = build.GetOutputDirectory(DefaultTfm, "Debug").ToString();
-
- // Act - 1
- var updatedContent = "@code { [Parameter] public string AParameter { get; set; } }";
- var tagHelperOutputCache = Path.Combine(intermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache");
-
- var generatedFile = Path.Combine(intermediateOutputPath, "Razor", "Views", "Shared", "NavMenu.razor.g.cs");
- var generatedDefinitionFile = Path.Combine(intermediateOutputPath, "RazorDeclaration", "Views", "Shared", "NavMenu.razor.g.cs");
-
- // Assert - 1
- var result = build.Execute();
-
- result.Should().Pass();
- var outputFile = Path.Combine(outputPath, "MvcWithComponents.dll");
- new FileInfo(outputFile).Should().Exist();
- var outputAssemblyThumbprint = FileThumbPrint.Create(outputFile);
-
- new FileInfo(generatedDefinitionFile).Should().Exist();
- var generatedDefinitionThumbprint = FileThumbPrint.Create(generatedDefinitionFile);
- new FileInfo(generatedFile).Should().Exist();
- var generatedFileThumbprint = FileThumbPrint.Create(generatedFile);
-
- new FileInfo(tagHelperOutputCache).Should().Exist();
- new FileInfo(tagHelperOutputCache).Should().Contain(@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
-
- var definitionThumbprint = FileThumbPrint.Create(tagHelperOutputCache);
-
- // Act - 2
- var page = Path.Combine(projectDirectory.Path, "Views", "Shared", "NavMenu.razor");
- File.WriteAllText(page, updatedContent);
-
- build = new BuildCommand(projectDirectory);
- result = build.Execute();
-
- // Assert - 2
- new FileInfo(outputFile).Should().Exist();
- Assert.NotEqual(outputAssemblyThumbprint, FileThumbPrint.Create(outputFile));
-
- new FileInfo(generatedDefinitionFile).Should().Exist();
- Assert.NotEqual(generatedDefinitionThumbprint, FileThumbPrint.Create(generatedDefinitionFile));
- new FileInfo(generatedFile).Should().Exist();
- Assert.NotEqual(generatedFileThumbprint, FileThumbPrint.Create(generatedFile));
-
- new FileInfo(tagHelperOutputCache).Should().Exist();
- new FileInfo(tagHelperOutputCache).Should().Contain(@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
-
- new FileInfo(tagHelperOutputCache).Should().Contain("AParameter");
-
- Assert.NotEqual(definitionThumbprint, FileThumbPrint.Create(tagHelperOutputCache));
- }
-
- [Fact]
- public void BuildComponents_DoesNotModifyFiles_IfFilesDoNotChange()
- {
- var testAsset = "RazorMvcWithComponents";
- var projectDirectory = CreateAspNetSdkTestAsset(testAsset);
-
- var build = new BuildCommand(projectDirectory);
-
- var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
- var outputPath = build.GetOutputDirectory(DefaultTfm, "Debug").ToString();
-
- // Act - 1
- var tagHelperOutputCache = Path.Combine(intermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache");
-
- var file = Path.Combine(projectDirectory.Path, "Views", "Shared", "NavMenu.razor.g.cs");
- var generatedFile = Path.Combine(intermediateOutputPath, "Razor", "Views", "Shared", "NavMenu.razor.g.cs");
- var generatedDefinitionFile = Path.Combine(intermediateOutputPath, "RazorDeclaration", "Views", "Shared", "NavMenu.razor.g.cs");
-
- // Assert - 1
- var result = build.Execute();
-
- result.Should().Pass();
- var outputFile = Path.Combine(outputPath, "MvcWithComponents.dll");
- new FileInfo(outputFile).Should().Exist();
- var outputAssemblyThumbprint = FileThumbPrint.Create(outputFile);
-
- new FileInfo(generatedDefinitionFile).Should().Exist();
- var generatedDefinitionThumbprint = FileThumbPrint.Create(generatedDefinitionFile);
- new FileInfo(generatedFile).Should().Exist();
- var generatedFileThumbprint = FileThumbPrint.Create(generatedFile);
-
- new FileInfo(tagHelperOutputCache).Should().Exist();
- new FileInfo(tagHelperOutputCache).Should().Contain(@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
-
- var definitionThumbprint = FileThumbPrint.Create(tagHelperOutputCache);
-
- // Act - 2
- result = build.Execute();
-
- // Assert - 2
- new FileInfo(outputFile).Should().Exist();
- Assert.Equal(outputAssemblyThumbprint, FileThumbPrint.Create(outputFile));
-
- new FileInfo(generatedDefinitionFile).Should().Exist();
- Assert.Equal(generatedDefinitionThumbprint, FileThumbPrint.Create(generatedDefinitionFile));
- new FileInfo(generatedFile).Should().Exist();
- Assert.Equal(generatedFileThumbprint, FileThumbPrint.Create(generatedFile));
-
- new FileInfo(tagHelperOutputCache).Should().Exist();
- new FileInfo(tagHelperOutputCache).Should().Contain(@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
-
- Assert.Equal(definitionThumbprint, FileThumbPrint.Create(tagHelperOutputCache));
- }
-
[Fact]
public void IncrementalBuild_WithP2P_WorksWhenBuildProjectReferencesIsDisabled()
{
@@ -386,7 +237,7 @@ public void IncrementalBuild_WithP2P_WorksWhenBuildProjectReferencesIsDisabled()
new FileInfo(Path.Combine(outputPath, "ClassLibrary.Views.pdb")).Should().Exist();
}
- [Fact]
+ [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/28780")]
public void Build_TouchesUpToDateMarkerFile()
{
var testAsset = "RazorClassLibrary";
diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIntegrationTest.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIntegrationTest.cs
index 2f3661e4a8e5..5b2af49bbf95 100644
--- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIntegrationTest.cs
+++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/BuildIntegrationTest.cs
@@ -39,7 +39,7 @@ private void Build_SimpleMvc_WithoutBuildServer_CanBuildSuccessfully()
var build = new BuildCommand(projectDirectory);
var outputPath = build.GetOutputDirectory(DefaultTfm, "Debug").ToString();
- build.Execute("/p:UseRazorBuildServer=false")
+ build.Execute("/p:_RazorSourceGeneratorWriteGeneratedOutput=true")
.Should()
.Pass()
.And.HaveStdOutContaining($"SimpleMvc -> {Path.Combine(projectDirectory.Path, outputPath, "SimpleMvc.Views.dll")}");
diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/DesignTimeBuildIntegrationTest.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/DesignTimeBuildIntegrationTest.cs
index d96e33fcecf2..323a108e536c 100644
--- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/DesignTimeBuildIntegrationTest.cs
+++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/DesignTimeBuildIntegrationTest.cs
@@ -42,7 +42,7 @@ public void DesignTimeBuild_DoesNotRunRazorTargets()
new FileInfo(Path.Combine(outputPath, "SimpleMvc.Views.pdb")).Should().NotExist();
}
- [Fact]
+ [Fact(Skip = "Skipping until https://github.com/dotnet/aspnetcore/issues/28825 is resolved.")]
public void RazorGenerateDesignTime_ReturnsRazorGenerateWithTargetPath()
{
var testAsset = "RazorSimpleMvc";
diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTest50.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTest50.cs
index e29420429a09..82c551b1e930 100644
--- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTest50.cs
+++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTest50.cs
@@ -18,6 +18,6 @@ public class MvcBuildIntegrationTest50 : MvcBuildIntegrationTestLegacy
public MvcBuildIntegrationTest50(ITestOutputHelper log) : base(log) {}
public override string TestProjectName => "SimpleMvc50";
- public override string TargetFramework => DefaultTfm;
+ public override string TargetFramework => "net5.0";
}
}
diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTestLegacy.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTestLegacy.cs
index e4c3c92c9620..a1f535f09a5f 100644
--- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTestLegacy.cs
+++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/MvcBuildIntegrationTestLegacy.cs
@@ -49,7 +49,6 @@ public virtual void Building_Project()
new FileInfo(
Path.Combine(intermediateOutputPath, $"{TestProjectName}.TagHelpers.output.cache")).Should().Contain(
@"""Name"":""SimpleMvc.SimpleTagHelper""");
-
}
[CoreMSBuildOnlyFact]
diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/PublishIntegrationTest.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/PublishIntegrationTest.cs
index 04eaefeb2c46..b3cd1dfd1ea6 100644
--- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/PublishIntegrationTest.cs
+++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/PublishIntegrationTest.cs
@@ -55,7 +55,7 @@ public void Publish_RazorCompileOnPublish_IsDefault()
new DirectoryInfo(Path.Combine(publishOutputPath, "Views")).Should().NotExist();
}
- [Fact]
+ [Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/28781")]
public void Publish_WithRazorCompileOnBuildFalse_PublishesAssembly()
{
var testAsset = "RazorSimpleMvc";
diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs
index 3ca088b4b0b9..4f3f76d66c52 100644
--- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs
+++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs
@@ -78,7 +78,7 @@ public void CanOverrideScopeIdentifiers()
File.Move(Path.Combine(projectDirectory.Path, "Components", "Pages", "Counter.razor.css"), styles);
var build = new BuildCommand(projectDirectory);
- build.Execute("/p:EnableDefaultScopedCssItems=false").Should().Pass();
+ build.Execute("/p:EnableDefaultScopedCssItems=false", "/p:_RazorSourceGeneratorWriteGeneratedOutput=true").Should().Pass();
var intermediateOutputPath = Path.Combine(build.GetBaseIntermediateDirectory().ToString(), "Debug", DefaultTfm);
@@ -215,7 +215,7 @@ public void Build_GeneratedComponentContainsScope()
var projectDirectory = CreateAspNetSdkTestAsset(testAsset);
var build = new BuildCommand(projectDirectory);
- build.Execute().Should().Pass();
+ build.Execute("/p:_RazorSourceGeneratorWriteGeneratedOutput=true").Should().Pass();
var intermediateOutputPath = Path.Combine(build.GetBaseIntermediateDirectory().ToString(), "Debug", DefaultTfm);
@@ -239,7 +239,7 @@ public void Build_RemovingScopedCssAndBuilding_UpdatesGeneratedCodeAndBundle()
var projectDirectory = CreateAspNetSdkTestAsset(testAsset);
var build = new BuildCommand(projectDirectory);
- build.Execute().Should().Pass();
+ build.Execute("/p:_RazorSourceGeneratorWriteGeneratedOutput=true").Should().Pass();
var intermediateOutputPath = Path.Combine(build.GetBaseIntermediateDirectory().ToString(), "Debug", DefaultTfm);
@@ -257,7 +257,7 @@ public void Build_RemovingScopedCssAndBuilding_UpdatesGeneratedCodeAndBundle()
File.Delete(Path.Combine(projectDirectory.Path, "Components", "Pages", "Counter.razor.css"));
build = new BuildCommand(projectDirectory);
- build.Execute().Should().Pass();
+ build.Execute("/p:_RazorSourceGeneratorWriteGeneratedOutput=true").Should().Pass();
new FileInfo(Path.Combine(intermediateOutputPath, "scopedcss", "Components", "Pages", "Counter.razor.rz.scp.css")).Should().NotExist();
new FileInfo(generatedCounter).Should().Exist();
@@ -299,7 +299,7 @@ public void Build_ScopedCssTransformation_AndBundling_IsIncremental()
// Act & Assert 1
var build = new BuildCommand(projectDirectory);
- build.Execute().Should().Pass();
+ build.Execute("/p:_RazorSourceGeneratorWriteGeneratedOutput=true").Should().Pass();
var intermediateOutputPath = Path.Combine(build.GetBaseIntermediateDirectory().ToString(), "Debug", DefaultTfm);
var directoryPath = Path.Combine(intermediateOutputPath, "scopedcss");