From 6aa9aee9ab1384f3ca620115704526b932da38ef Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Thu, 19 Sep 2024 17:35:35 -0700 Subject: [PATCH] Calculate SuppressAddComponentParameter in tooling (#10763) Fixes #10736 Chris did a good breakdown of what this value is used for #10736 (comment) --- .../src/CSharp/CompilationExtensions.cs | 19 +++++++ .../RazorCodeGenerationOptionsBuilder.cs | 4 ++ .../src/Language/RazorConfiguration.cs | 12 +++-- .../RazorSourceGenerator.Helpers.cs | 5 +- .../RazorSourceGenerator.RazorProviders.cs | 17 +++++-- .../SourceGenerators/RazorSourceGenerator.cs | 20 ++------ .../RazorSourceGeneratorTests.cs | 2 + .../Kendo.Mvc.Examples.project.razor.json | 2 +- .../Resources/project.razor.json | 2 +- .../RazorWorkspaceListenerBase.cs | 2 +- .../Formatters/RazorConfigurationFormatter.cs | 26 +++++----- .../MessagePack/SerializationFormat.cs | 2 +- .../Utilities}/RazorProjectInfoFactory.cs | 49 ++++++++++--------- .../ProjectSystem/RemoteDocumentSnapshot.cs | 2 +- .../ProjectSystem/RemoteProjectSnapshot.cs | 36 ++++++++++---- .../DefaultWindowsRazorProjectHost.cs | 2 +- .../RazorProjectInfoSerializerTest.cs | 1 + .../Formatting_NetFx/FormattingTestBase.cs | 2 +- .../ObjectReaders.cs | 4 +- .../ObjectWriters.cs | 3 ++ .../SerializationFormat.cs | 2 +- 21 files changed, 134 insertions(+), 80 deletions(-) create mode 100644 src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/CompilationExtensions.cs rename src/Razor/src/{Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace => Microsoft.AspNetCore.Razor.ProjectEngineHost/Utilities}/RazorProjectInfoFactory.cs (85%) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/CompilationExtensions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/CompilationExtensions.cs new file mode 100644 index 00000000000..dc5dcd44c86 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/CompilationExtensions.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.CodeAnalysis.Razor.Compiler.CSharp; + +internal static class CompilationExtensions +{ + public static bool HasAddComponentParameter(this Compilation compilation) + { + return compilation.GetTypesByMetadataName("Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder") + .Any(static t => + t.DeclaredAccessibility == Accessibility.Public && + t.GetMembers("AddComponentParameter") + .Any(static m => m.DeclaredAccessibility == Accessibility.Public)); + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptionsBuilder.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptionsBuilder.cs index 4f35d92c094..0e9caed4b26 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptionsBuilder.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptionsBuilder.cs @@ -159,6 +159,10 @@ public RazorCodeGenerationOptionsBuilder(RazorConfiguration configuration) ArgHelper.ThrowIfNull(configuration); Configuration = configuration; + if (configuration.SuppressAddComponentParameter) + { + _flags.SetFlag(RazorCodeGenerationOptionsFlags.SuppressAddComponentParameter); + } } public RazorCodeGenerationOptionsBuilder(RazorCodeGenerationOptionsFlags flags) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs index a1b1ba4b487..83cd64698d1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs @@ -11,20 +11,23 @@ public sealed record class RazorConfiguration( RazorLanguageVersion LanguageVersion, string ConfigurationName, ImmutableArray Extensions, - LanguageServerFlags? LanguageServerFlags = null, - bool UseConsolidatedMvcViews = true) + bool UseConsolidatedMvcViews = true, + bool SuppressAddComponentParameter = false, + LanguageServerFlags? LanguageServerFlags = null) { public static readonly RazorConfiguration Default = new( RazorLanguageVersion.Latest, ConfigurationName: "unnamed", Extensions: [], - LanguageServerFlags: null, - UseConsolidatedMvcViews: true); + UseConsolidatedMvcViews: true, + SuppressAddComponentParameter: false, + LanguageServerFlags: null); public bool Equals(RazorConfiguration? other) => other is not null && LanguageVersion == other.LanguageVersion && ConfigurationName == other.ConfigurationName && + SuppressAddComponentParameter == other.SuppressAddComponentParameter && LanguageServerFlags == other.LanguageServerFlags && UseConsolidatedMvcViews == other.UseConsolidatedMvcViews && Extensions.SequenceEqual(other.Extensions); @@ -35,6 +38,7 @@ public override int GetHashCode() hash.Add(LanguageVersion); hash.Add(ConfigurationName); hash.Add(Extensions); + hash.Add(SuppressAddComponentParameter); hash.Add(UseConsolidatedMvcViews); hash.Add(LanguageServerFlags); return hash; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.Helpers.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.Helpers.cs index bc0e302982d..6b789f69744 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.Helpers.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.Helpers.cs @@ -87,8 +87,7 @@ private static StaticCompilationTagHelperFeature GetStaticTagHelperFeature(Compi private static SourceGeneratorProjectEngine GetGenerationProjectEngine( SourceGeneratorProjectItem item, IEnumerable imports, - RazorSourceGenerationOptions razorSourceGeneratorOptions, - bool isAddComponentParameterAvailable) + RazorSourceGenerationOptions razorSourceGeneratorOptions) { var fileSystem = new VirtualRazorProjectFileSystem(); fileSystem.Add(item); @@ -107,7 +106,7 @@ private static SourceGeneratorProjectEngine GetGenerationProjectEngine( options.SuppressMetadataSourceChecksumAttributes = !razorSourceGeneratorOptions.GenerateMetadataSourceChecksumAttributes; options.SupportLocalizedComponentNames = razorSourceGeneratorOptions.SupportLocalizedComponentNames; options.SuppressUniqueIds = razorSourceGeneratorOptions.TestSuppressUniqueIds; - options.SuppressAddComponentParameter = !isAddComponentParameterAvailable; + options.SuppressAddComponentParameter = razorSourceGeneratorOptions.Configuration.SuppressAddComponentParameter; })); CompilerFeatures.Register(b); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs index 7daf0ba4abf..1cd315c9d28 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs @@ -2,21 +2,24 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Immutable; using System.IO; +using System.Linq; using System.Text; using System.Threading; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Razor.Compiler.CSharp; namespace Microsoft.NET.Sdk.Razor.SourceGenerators { public partial class RazorSourceGenerator { - private (RazorSourceGenerationOptions?, Diagnostic?) ComputeRazorSourceGeneratorOptions(((AnalyzerConfigOptionsProvider, ParseOptions), bool) pair, CancellationToken ct) + private (RazorSourceGenerationOptions?, Diagnostic?) ComputeRazorSourceGeneratorOptions((((AnalyzerConfigOptionsProvider, ParseOptions), ImmutableArray), bool) pair, CancellationToken ct) { - var ((options, parseOptions), isSuppressed) = pair; + var (((options, parseOptions), references), isSuppressed) = pair; var globalOptions = options.GlobalOptions; if (isSuppressed) @@ -42,7 +45,15 @@ public partial class RazorSourceGenerator razorLanguageVersion = RazorLanguageVersion.Latest; } - var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName ?? "default", Extensions: [], UseConsolidatedMvcViews: true); + var minimalReferences = references + .Where(r => r.Display is { } display && display.EndsWith("Microsoft.AspNetCore.Components.dll", StringComparison.Ordinal)) + .ToImmutableArray(); + + var isComponentParameterSupported = minimalReferences.Length == 0 + ? false + : CSharpCompilation.Create("components", references: minimalReferences).HasAddComponentParameter(); + + var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName ?? "default", Extensions: [], UseConsolidatedMvcViews: true, SuppressAddComponentParameter: !isComponentParameterSupported); var razorSourceGenerationOptions = new RazorSourceGenerationOptions() { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs index 0fe65e3eb60..9d3eff80c26 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -44,6 +43,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var razorSourceGeneratorOptions = analyzerConfigOptions .Combine(parseOptions) + .Combine(metadataRefs.Collect()) .SuppressIfNeeded(isGeneratorSuppressed) .Select(ComputeRazorSourceGeneratorOptions) .ReportDiagnostics(context); @@ -235,30 +235,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var razorHostOutputsEnabled = analyzerConfigOptions.CheckGlobalFlagSet("EnableRazorHostOutputs"); var withOptionsDesignTime = withOptions.EmptyOrCachedWhen(razorHostOutputsEnabled, false); - var isAddComponentParameterAvailable = metadataRefs - .Where(r => r.Display is { } display && display.EndsWith("Microsoft.AspNetCore.Components.dll", StringComparison.Ordinal)) - .Collect() - .Select((refs, _) => - { - var compilation = CSharpCompilation.Create("components", references: refs); - return compilation.GetTypesByMetadataName("Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder") - .Any(static t => - t.DeclaredAccessibility == Accessibility.Public && - t.GetMembers("AddComponentParameter") - .Any(static m => m.DeclaredAccessibility == Accessibility.Public)); - }); - IncrementalValuesProvider<(string, SourceGeneratorRazorCodeDocument)> processed(bool designTime) { return (designTime ? withOptionsDesignTime : withOptions) - .Combine(isAddComponentParameterAvailable) .Select((pair, _) => { - var (((sourceItem, imports), razorSourceGeneratorOptions), isAddComponentParameterAvailable) = pair; + var ((sourceItem, imports), razorSourceGeneratorOptions) = pair; RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStart(sourceItem.RelativePhysicalPath); - var projectEngine = GetGenerationProjectEngine(sourceItem, imports, razorSourceGeneratorOptions, isAddComponentParameterAvailable); + var projectEngine = GetGenerationProjectEngine(sourceItem, imports, razorSourceGeneratorOptions); var document = projectEngine.ProcessInitialParse(sourceItem, designTime); 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 9d5de9e89e6..6bcc67c34bd 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 @@ -1160,6 +1160,7 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. Assert.Equal(2, result.GeneratedSources.Length); Assert.Collection(eventListener.Events, + e => Assert.Equal("ComputeRazorSourceGeneratorOptions", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromReferencesStart", e.EventName), @@ -3260,6 +3261,7 @@ public async Task IncrementalCompilation_OnlyCompilationRuns_When_MetadataRefere // reference causes the compilation to change so we re-run tag helper discovery there // but we didn't re-check the actual reference itself Assert.Collection(eventListener.Events, + e => Assert.Equal("ComputeRazorSourceGeneratorOptions", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStart", e.EventName), e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName)); } diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/Telerik/Kendo.Mvc.Examples.project.razor.json b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/Telerik/Kendo.Mvc.Examples.project.razor.json index 00ca3271787..9104b499f89 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/Telerik/Kendo.Mvc.Examples.project.razor.json +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/Telerik/Kendo.Mvc.Examples.project.razor.json @@ -1,5 +1,5 @@ { - "__Version": 5, + "__Version": 6, "ProjectKey": "C:\\Users\\admin\\location\\Kendo.Mvc.Examples\\obj\\Debug\\net7.0\\", "FilePath": "C:\\Users\\admin\\location\\Kendo.Mvc.Examples\\Kendo.Mvc.Examples.csproj", "Configuration": { diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/project.razor.json b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/project.razor.json index 2521817d3c0..d1525d22f3a 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/project.razor.json +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Resources/project.razor.json @@ -1,5 +1,5 @@ { - "__Version": 5, + "__Version": 6, "ProjectKey": "C:\\Users\\admin\\location\\blazorserver\\obj\\Debug\\net7.0\\", "FilePath": "C:\\Users\\admin\\location\\blazorserver\\blazorserver.csproj", "Configuration": { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace/RazorWorkspaceListenerBase.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace/RazorWorkspaceListenerBase.cs index 7ab3ecf2a4b..753ad07c46d 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace/RazorWorkspaceListenerBase.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace/RazorWorkspaceListenerBase.cs @@ -279,7 +279,7 @@ private static async Task ProcessWorkCoreAsync(ImmutableArray work, Stream private static async Task ReportUpdateProjectAsync(Stream stream, Project project, ILogger logger, CancellationToken cancellationToken) { logger.LogTrace("Serializing information for {projectId}", project.Id); - var projectInfo = await RazorProjectInfoFactory.ConvertAsync(project, logger, cancellationToken).ConfigureAwait(false); + var projectInfo = await RazorProjectInfoFactory.ConvertAsync(project, cancellationToken).ConfigureAwait(false); if (projectInfo is null) { logger.LogTrace("Skipped writing data for {projectId}", project.Id); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/Formatters/RazorConfigurationFormatter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/Formatters/RazorConfigurationFormatter.cs index 94a229eedb7..6232c1fabdb 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/Formatters/RazorConfigurationFormatter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/Formatters/RazorConfigurationFormatter.cs @@ -22,14 +22,10 @@ public override RazorConfiguration Deserialize(ref MessagePackReader reader, Ser var configurationName = CachedStringFormatter.Instance.Deserialize(ref reader, options) ?? string.Empty; var languageVersionText = CachedStringFormatter.Instance.Deserialize(ref reader, options) ?? string.Empty; + var suppressAddComponentParameter = reader.ReadBoolean(); + var useConsolidatedMvcViews = reader.ReadBoolean(); - count -= 2; - - if (reader.NextMessagePackType is MessagePackType.Boolean) - { - reader.ReadBoolean(); // forceRuntimeCodeGeneration - count -= 1; - } + count -= 4; using var builder = new PooledArrayBuilder(); @@ -45,14 +41,19 @@ public override RazorConfiguration Deserialize(ref MessagePackReader reader, Ser ? version : RazorLanguageVersion.Version_2_1; - return new(languageVersion, configurationName, extensions); + return new( + languageVersion, + configurationName, + extensions, + UseConsolidatedMvcViews: useConsolidatedMvcViews, + SuppressAddComponentParameter: suppressAddComponentParameter); } public override void Serialize(ref MessagePackWriter writer, RazorConfiguration value, SerializerCachingOptions options) { - // Write 3 values + 1 value per extension. + // Write 4 values + 1 value per extension. var extensions = value.Extensions; - var count = extensions.Length + 2; + var count = extensions.Length + 4; writer.WriteArrayHeader(count); @@ -67,7 +68,10 @@ public override void Serialize(ref MessagePackWriter writer, RazorConfiguration CachedStringFormatter.Instance.Serialize(ref writer, value.LanguageVersion.ToString(), options); } - count -= 2; + writer.Write(value.SuppressAddComponentParameter); + writer.Write(value.UseConsolidatedMvcViews); + + count -= 4; for (var i = 0; i < count; i++) { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/SerializationFormat.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/SerializationFormat.cs index bb550776606..0fc5c52667c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/SerializationFormat.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/MessagePack/SerializationFormat.cs @@ -9,5 +9,5 @@ internal static class SerializationFormat // or any of the types that compose it changes. This includes: RazorConfiguration, // ProjectWorkspaceState, TagHelperDescriptor, and DocumentSnapshotHandle. // NOTE: If this version is changed, a coordinated insertion is required between Roslyn and Razor for the C# extension. - public const int Version = 5; + public const int Version = 6; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace/RazorProjectInfoFactory.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Utilities/RazorProjectInfoFactory.cs similarity index 85% rename from src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace/RazorProjectInfoFactory.cs rename to src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Utilities/RazorProjectInfoFactory.cs index c99a25ec283..37a68df1de7 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace/RazorProjectInfoFactory.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Utilities/RazorProjectInfoFactory.cs @@ -1,23 +1,26 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Serialization; using Microsoft.AspNetCore.Razor.Telemetry; -using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Razor; -using Microsoft.Extensions.Logging; +using Microsoft.CodeAnalysis.Razor.Compiler.CSharp; -namespace Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace; +namespace Microsoft.AspNetCore.Razor.Utilities; internal static class RazorProjectInfoFactory { @@ -30,19 +33,17 @@ static RazorProjectInfoFactory() : StringComparison.OrdinalIgnoreCase; } - public static async Task ConvertAsync(Project project, ILogger? logger, CancellationToken cancellationToken) + public static async Task ConvertAsync(Project project, CancellationToken cancellationToken) { var projectPath = Path.GetDirectoryName(project.FilePath); if (projectPath is null) { - logger?.LogInformation("projectPath is null, skip conversion for {projectId}", project.Id); return null; } var intermediateOutputPath = Path.GetDirectoryName(project.CompilationOutputInfo.AssemblyPath); if (intermediateOutputPath is null) { - logger?.LogInformation("intermediatePath is null, skip conversion for {projectId}", project.Id); return null; } @@ -52,23 +53,19 @@ static RazorProjectInfoFactory() // Not a razor project if (documents.Length == 0) { - if (project.DocumentIds.Count == 0) - { - logger?.LogInformation("No razor documents for {projectId}", project.Id); - } - else - { - logger?.LogTrace("No documents in {projectId}", project.Id); - } - return null; } var csharpLanguageVersion = (project.ParseOptions as CSharpParseOptions)?.LanguageVersion ?? LanguageVersion.Default; - var options = project.AnalyzerOptions.AnalyzerConfigOptionsProvider; - var configuration = ComputeRazorConfigurationOptions(options, logger, out var defaultNamespace); + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + if (compilation is null) + { + return null; + } + var options = project.AnalyzerOptions.AnalyzerConfigOptionsProvider; + var configuration = ComputeRazorConfigurationOptions(options, compilation, out var defaultNamespace); var fileSystem = RazorProjectFileSystem.Create(projectPath); var defaultConfigure = (RazorProjectEngineBuilder builder) => @@ -104,7 +101,7 @@ static RazorProjectInfoFactory() documents: documents); } - private static RazorConfiguration ComputeRazorConfigurationOptions(AnalyzerConfigOptionsProvider options, ILogger? logger, out string defaultNamespace) + private static RazorConfiguration ComputeRazorConfigurationOptions(AnalyzerConfigOptionsProvider options, Compilation compilation, out string defaultNamespace) { // See RazorSourceGenerator.RazorProviders.cs @@ -119,11 +116,17 @@ private static RazorConfiguration ComputeRazorConfigurationOptions(AnalyzerConfi if (!globalOptions.TryGetValue("build_property.RazorLangVersion", out var razorLanguageVersionString) || !RazorLanguageVersion.TryParse(razorLanguageVersionString, out var razorLanguageVersion)) { - logger?.LogTrace("Using default of latest language version"); razorLanguageVersion = RazorLanguageVersion.Latest; } - var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName, Extensions: [], UseConsolidatedMvcViews: true); + var suppressAddComponentParameter = !compilation.HasAddComponentParameter(); + + var razorConfiguration = new RazorConfiguration( + razorLanguageVersion, + configurationName, + Extensions: [], + UseConsolidatedMvcViews: true, + suppressAddComponentParameter); defaultNamespace = rootNamespace ?? "ASP"; // TODO: Source generator does this. Do we want it? @@ -182,7 +185,7 @@ private static string GetTargetPath(string documentFilePath, string normalizedPr private static bool TryGetFileKind(string filePath, [NotNullWhen(true)] out string? fileKind) { - var extension = Path.GetExtension(filePath.AsSpan()); + var extension = Path.GetExtension(filePath); if (extension.Equals(".cshtml", s_stringComparison)) { @@ -216,8 +219,8 @@ private static bool TryGetRazorFileName(string? filePath, [NotNullWhen(true)] ou var path = filePath.AsSpan(); // Generated files have a path like: virtualcsharp-razor:///e:/Scratch/RazorInConsole/Goo.cshtml__virtual.cs - if (path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && - (path.EndsWith(generatedRazorExtension, s_stringComparison) || path.EndsWith(generatedCshtmlExtension, s_stringComparison))) + if (path.StartsWith(prefix.AsSpan(), StringComparison.OrdinalIgnoreCase) && + (path.EndsWith(generatedRazorExtension.AsSpan(), s_stringComparison) || path.EndsWith(generatedCshtmlExtension.AsSpan(), s_stringComparison))) { // Go through the file path normalizer because it also does Uri decoding, and we're converting from a Uri to a path // but "new Uri(filePath).LocalPath" seems wasteful diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs index bf65778cd50..c8b9537ba4c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -64,7 +64,7 @@ public async Task GetGeneratedOutputAsync(bool _) // and simply compiles when asked, and if a new document snapshot comes in, we compile again. This is presumably worse for perf // but since we don't expect users to ever use cohosting without source generators, it's fine for now. - var projectEngine = _projectSnapshot.GetProjectEngine_CohostOnly(); + var projectEngine = await _projectSnapshot.GetProjectEngine_CohostOnlyAsync(CancellationToken.None).ConfigureAwait(false); var tagHelpers = await _projectSnapshot.GetTagHelpersAsync(CancellationToken.None).ConfigureAwait(false); var imports = await DocumentState.GetImportsAsync(this, projectEngine).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs index 6d5d2ec6040..c5d51dce06c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs @@ -18,7 +18,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis.Razor.Compiler.CSharp; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.Threading; namespace Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; @@ -29,8 +31,8 @@ internal class RemoteProjectSnapshot : IProjectSnapshot private readonly Project _project; private readonly DocumentSnapshotFactory _documentSnapshotFactory; private readonly ITelemetryReporter _telemetryReporter; - private readonly Lazy _lazyConfiguration; - private readonly Lazy _lazyProjectEngine; + private readonly AsyncLazy _lazyConfiguration; + private readonly AsyncLazy _lazyProjectEngine; private ImmutableArray _tagHelpers; @@ -41,11 +43,12 @@ public RemoteProjectSnapshot(Project project, DocumentSnapshotFactory documentSn _telemetryReporter = telemetryReporter; Key = _project.ToProjectKey(); - _lazyConfiguration = new Lazy(CreateRazorConfiguration); - _lazyProjectEngine = new Lazy(() => + _lazyConfiguration = new AsyncLazy(CreateRazorConfigurationAsync, joinableTaskFactory: null); + _lazyProjectEngine = new AsyncLazy(async () => { + var configuration = await _lazyConfiguration.GetValueAsync(); return ProjectEngineFactories.DefaultProvider.Create( - _lazyConfiguration.Value, + configuration, rootDirectoryPath: Path.GetDirectoryName(FilePath).AssumeNotNull(), configure: builder => { @@ -53,7 +56,8 @@ public RemoteProjectSnapshot(Project project, DocumentSnapshotFactory documentSn builder.SetCSharpLanguageVersion(CSharpLanguageVersion); builder.SetSupportLocalizedComponentNames(); }); - }); + }, + joinableTaskFactory: null); } public RazorConfiguration Configuration => throw new InvalidOperationException("Should not be called for cohosted projects."); @@ -96,7 +100,8 @@ public async ValueTask> GetTagHelpersAsync(C { if (_tagHelpers.IsDefault) { - var computedTagHelpers = await ComputeTagHelpersAsync(_project, _lazyProjectEngine.Value, _telemetryReporter, cancellationToken); + var projectEngine = await _lazyProjectEngine.GetValueAsync(cancellationToken); + var computedTagHelpers = await ComputeTagHelpersAsync(_project, projectEngine, _telemetryReporter, cancellationToken); ImmutableInterlocked.InterlockedInitialize(ref _tagHelpers, computedTagHelpers); } @@ -138,9 +143,9 @@ public bool TryGetDocument(string filePath, [NotNullWhen(true)] out IDocumentSna /// NOTE: To be called only from CohostDocumentSnapshot.GetGeneratedOutputAsync(). Will be removed when that method uses the source generator directly. /// /// - internal RazorProjectEngine GetProjectEngine_CohostOnly() => _lazyProjectEngine.Value; + internal Task GetProjectEngine_CohostOnlyAsync(CancellationToken cancellationToken) => _lazyProjectEngine.GetValueAsync(cancellationToken); - private RazorConfiguration CreateRazorConfiguration() + private async Task CreateRazorConfigurationAsync() { // See RazorSourceGenerator.RazorProviders.cs @@ -156,6 +161,17 @@ private RazorConfiguration CreateRazorConfiguration() razorLanguageVersion = RazorLanguageVersion.Latest; } - return new(razorLanguageVersion, configurationName, Extensions: [], UseConsolidatedMvcViews: true); + var compilation = await _project.GetCompilationAsync().ConfigureAwait(false); + + var suppressAddComponentParameter = compilation is null + ? false + : !compilation.HasAddComponentParameter(); + + return new( + razorLanguageVersion, + configurationName, + Extensions: [], + UseConsolidatedMvcViews: true, + suppressAddComponentParameter); } } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs index d3514dc58de..5b499fb9c44 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs @@ -166,7 +166,7 @@ internal static bool TryGetConfiguration( languageVersion, configurationItem.Key, extensions, - languageServerFlags); + LanguageServerFlags: languageServerFlags); return true; } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace.Test/RazorProjectInfoSerializerTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace.Test/RazorProjectInfoSerializerTest.cs index 465d490de1f..0b5ea2a7a73 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace.Test/RazorProjectInfoSerializerTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace.Test/RazorProjectInfoSerializerTest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs index 8e070db10f5..6a4ae7846fc 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs @@ -290,7 +290,7 @@ @using Microsoft.AspNetCore.Components.Web ]); var projectEngine = RazorProjectEngine.Create( - new RazorConfiguration(RazorLanguageVersion.Latest, "TestConfiguration", Extensions: [], new LanguageServerFlags(forceRuntimeCodeGeneration)), + new RazorConfiguration(RazorLanguageVersion.Latest, "TestConfiguration", Extensions: [], LanguageServerFlags: new LanguageServerFlags(forceRuntimeCodeGeneration)), projectFileSystem, builder => { diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectReaders.cs b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectReaders.cs index e1726562363..91ae5629e82 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectReaders.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectReaders.cs @@ -42,6 +42,8 @@ public static RazorConfiguration ReadConfigurationFromProperties(JsonDataReader { var configurationName = reader.ReadNonNullString(nameof(RazorConfiguration.ConfigurationName)); var languageVersionText = reader.ReadNonNullString(nameof(RazorConfiguration.LanguageVersion)); + var suppressAddComponentParameter = reader.ReadBooleanOrFalse(nameof(RazorConfiguration.SuppressAddComponentParameter)); + var useConsolidatedMvcViews = reader.ReadBooleanOrTrue(nameof(RazorConfiguration.UseConsolidatedMvcViews)); var extensions = reader.ReadImmutableArrayOrEmpty(nameof(RazorConfiguration.Extensions), static r => { @@ -53,7 +55,7 @@ public static RazorConfiguration ReadConfigurationFromProperties(JsonDataReader ? version : RazorLanguageVersion.Version_2_1; - return new(languageVersion, configurationName, extensions); + return new(languageVersion, configurationName, extensions, SuppressAddComponentParameter: suppressAddComponentParameter); } public static RazorDiagnostic ReadDiagnostic(JsonDataReader reader) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectWriters.cs b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectWriters.cs index c8998ca6fc9..aad91449d2b 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectWriters.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/ObjectWriters.cs @@ -35,6 +35,9 @@ public static void WriteProperties(JsonDataWriter writer, RazorConfiguration val writer.Write(nameof(value.LanguageVersion), languageVersionText); + writer.WriteIfNotFalse(nameof(value.SuppressAddComponentParameter), value.SuppressAddComponentParameter); + writer.WriteIfNotTrue(nameof(value.UseConsolidatedMvcViews), value.UseConsolidatedMvcViews); + writer.WriteArrayIfNotNullOrEmpty(nameof(value.Extensions), value.Extensions, static (w, v) => w.Write(v.ExtensionName)); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/SerializationFormat.cs b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/SerializationFormat.cs index 1be32b7f927..c6a8db23f4a 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/SerializationFormat.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/SerializationFormat.cs @@ -9,5 +9,5 @@ internal static class SerializationFormat // or any of the types that compose it changes. This includes: RazorConfiguration, // ProjectWorkspaceState, TagHelperDescriptor, and DocumentSnapshotHandle. // NOTE: If this version is changed, a coordinated insertion is required between Roslyn and Razor for the C# extension. - public const int Version = 5; + public const int Version = 6; }