From 870789440648236709b89915c4c295093e321e98 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 8 Feb 2023 10:16:38 +0100 Subject: [PATCH 01/22] Add reference to `ExternalAccess.RazorCompiler` Emit design-time outputs from the source generator Emit host outputs from the source generator Generate both design-time and runtime outputs Switch to implementation output --- eng/Versions.props | 1 + src/Compiler/Directory.Packages.props | 1 + ...soft.NET.Sdk.Razor.SourceGenerators.csproj | 1 + .../RazorSourceGenerationOptions.cs | 2 +- .../RazorSourceGenerator.cs | 189 +++++++----------- .../RazorSourceGeneratorEventSource.cs | 4 +- 6 files changed, 83 insertions(+), 115 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 63e26ea3692..e454f991816 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -136,6 +136,7 @@ 4.3.0 3.3.4 $(RoslynPackageVersion) + $(RoslynPackageVersion) $(RoslynPackageVersion) $(RoslynPackageVersion) $(RoslynPackageVersion) diff --git a/src/Compiler/Directory.Packages.props b/src/Compiler/Directory.Packages.props index 118ed5d41c6..4e289cf0219 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 b5a9a50af34..33b24c31967 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.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs index b813fdba645..cef86f79574 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs @@ -2,15 +2,14 @@ // 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.Collections.Immutable; -using System.Diagnostics; using System.IO; using System.Linq; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler; namespace Microsoft.NET.Sdk.Razor.SourceGenerators { @@ -26,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,12 +67,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var generatedDeclarationCode = componentFiles .Combine(importFiles.Collect()) .Combine(razorSourceGeneratorOptions) - .WithLambdaComparer((old, @new) => (old.Right.Equals(@new.Right) && old.Left.Left.Equals(@new.Left.Left) && old.Left.Right.SequenceEqual(@new.Left.Right)), (a) => a.GetHashCode()) .Select(static (pair, _) => { - var ((sourceItem, importFiles), razorSourceGeneratorOptions) = pair; - RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.RelativePhysicalPath); + RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.FilePath); var projectEngine = GetDeclarationProjectEngine(sourceItem, importFiles, razorSourceGeneratorOptions); @@ -81,69 +78,57 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var result = codeGen.GetCSharpDocument().GeneratedCode; - RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStop(sourceItem.RelativePhysicalPath); + RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStop(sourceItem.FilePath); - return (result, sourceItem.RelativePhysicalPath); + return result; }); var generatedDeclarationSyntaxTrees = generatedDeclarationCode .Combine(parseOptions) - .Select(static (pair, ct) => + .Select(static (pair, _) => { - var ((generatedDeclarationCode, filePath), parseOptions) = pair; - return CSharpSyntaxTree.ParseText(generatedDeclarationCode, (CSharpParseOptions)parseOptions, filePath, cancellationToken: ct); + var (generatedDeclarationCode, parseOptions) = pair; + return CSharpSyntaxTree.ParseText(generatedDeclarationCode, (CSharpParseOptions)parseOptions); }); - var tagHelpersFromComponents = generatedDeclarationSyntaxTrees - .Combine(compilation) + var tagHelpersFromCompilation = compilation + .Combine(generatedDeclarationSyntaxTrees.Collect()) .Combine(razorSourceGeneratorOptions) - .SelectMany(static (pair, ct) => + .Select(static (pair, _) => { + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStart(); - var ((generatedDeclarationSyntaxTree, compilation), razorSourceGeneratorOptions) = pair; - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromComponentStart(generatedDeclarationSyntaxTree.FilePath); + var ((compilation, generatedDeclarationSyntaxTrees), razorSourceGeneratorOptions) = pair; var tagHelperFeature = new StaticCompilationTagHelperFeature(); var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); - var compilationWithDeclarations = compilation.AddSyntaxTrees(generatedDeclarationSyntaxTree); - - // try and find the specific root class this component is declaring, falling back to the assembly if for any reason the code is not in the shape we expect - ISymbol targetSymbol = compilationWithDeclarations.Assembly; - var root = generatedDeclarationSyntaxTree.GetRoot(ct); - if (root is CompilationUnitSyntax { Members: [NamespaceDeclarationSyntax { Members: [ClassDeclarationSyntax classSyntax, ..] }, ..] }) - { - var declaredClass = compilationWithDeclarations.GetSemanticModel(generatedDeclarationSyntaxTree).GetDeclaredSymbol(classSyntax, ct); - Debug.Assert(declaredClass is null || declaredClass is { AllInterfaces: [{ Name: "IComponent" }, ..] }); - targetSymbol = declaredClass ?? targetSymbol; - } + var compilationWithDeclarations = compilation.AddSyntaxTrees(generatedDeclarationSyntaxTrees); tagHelperFeature.Compilation = compilationWithDeclarations; - tagHelperFeature.TargetSymbol = targetSymbol; + tagHelperFeature.TargetSymbol = compilationWithDeclarations.Assembly; - var result = tagHelperFeature.GetDescriptors(); - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromComponentStop(generatedDeclarationSyntaxTree.FilePath); + var result = (IList)tagHelperFeature.GetDescriptors(); + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStop(); return result; - }); - - var tagHelpersFromCompilation = compilation - .Combine(razorSourceGeneratorOptions) - .Select(static (pair, _) => + }) + .WithLambdaComparer(static (a, b) => { - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStart(); - - var (compilation, razorSourceGeneratorOptions) = pair; + if (a.Count != b.Count) + { + return false; + } - var tagHelperFeature = new StaticCompilationTagHelperFeature(); - var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); + for (var i = 0; i < a.Count; i++) + { + if (!a[i].Equals(b[i])) + { + return false; + } + } - tagHelperFeature.Compilation = compilation; - tagHelperFeature.TargetSymbol = compilation.Assembly; - - var result = tagHelperFeature.GetDescriptors(); - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStop(); - return result; - }); + return true; + }, getHashCode: static a => a.Count); var tagHelpersFromReferences = compilation .Combine(razorSourceGeneratorOptions) @@ -186,7 +171,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var tagHelperFeature = new StaticCompilationTagHelperFeature(); var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); - using var pool = ArrayBuilderPool.GetPooledObject(out var descriptors); + List descriptors = new(); tagHelperFeature.Compilation = compilation; foreach (var reference in compilation.References) { @@ -198,84 +183,60 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStop(); - return descriptors.ToImmutable(); + return (ICollection)descriptors; }); - var allTagHelpers = tagHelpersFromComponents.Collect() - .Combine(tagHelpersFromCompilation) + var allTagHelpers = tagHelpersFromCompilation .Combine(tagHelpersFromReferences) .Select(static (pair, _) => { - var ((tagHelpersFromComponents, tagHelpersFromCompilation), tagHelpersFromReferences) = pair; - var count = tagHelpersFromCompilation.Length + tagHelpersFromReferences.Length + tagHelpersFromComponents.Length; + var (tagHelpersFromCompilation, tagHelpersFromReferences) = pair; + var count = tagHelpersFromCompilation.Count + tagHelpersFromReferences.Count; if (count == 0) { - return ImmutableArray.Empty; + return Array.Empty(); } - using var pool = ArrayBuilderPool.GetPooledObject(out var allTagHelpers); - allTagHelpers.AddRange(tagHelpersFromCompilation); - allTagHelpers.AddRange(tagHelpersFromReferences); - allTagHelpers.AddRange(tagHelpersFromComponents); + var allTagHelpers = new TagHelperDescriptor[count]; + tagHelpersFromCompilation.CopyTo(allTagHelpers, 0); + tagHelpersFromReferences.CopyTo(allTagHelpers, tagHelpersFromCompilation.Count); - return allTagHelpers.ToImmutable(); + return allTagHelpers; }); - var generatedOutput = sourceItems - .Combine(importFiles.Collect()) - .WithLambdaComparer((old, @new) => old.Left.Equals(@new.Left) && old.Right.SequenceEqual(@new.Right), (a) => a.GetHashCode()) - .Combine(razorSourceGeneratorOptions) - .Select(static (pair, _) => - { - var ((sourceItem, imports), razorSourceGeneratorOptions) = pair; - RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStart(sourceItem.RelativePhysicalPath); - - var projectEngine = GetGenerationProjectEngine(sourceItem, imports, razorSourceGeneratorOptions); - - var document = projectEngine.ProcessInitialParse(sourceItem); - - RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStop(sourceItem.RelativePhysicalPath); - return (projectEngine, sourceItem.RelativePhysicalPath, document); - }) - - // Add the tag helpers in, but ignore if they've changed or not, only reprocessing the actual document changed + IncrementalValuesProvider<(string hintName, RazorCodeDocument codeDocument)> codeDocuments(bool designTime) => sourceItems + .Combine(importFiles.Collect()) .Combine(allTagHelpers) - .WithLambdaComparer((old, @new) => old.Left.Equals(@new.Left), (item) => item.GetHashCode()) + .Combine(razorSourceGeneratorOptions) .Select((pair, _) => { - var ((projectEngine, filePath, codeDocument), allTagHelpers) = pair; - RazorSourceGeneratorEventSource.Log.RewriteTagHelpersStart(filePath); + var (((sourceItem, imports), allTagHelpers), razorSourceGeneratorOptions) = pair; - codeDocument = projectEngine.ProcessTagHelpers(codeDocument, allTagHelpers, checkForIdempotency: false); + var kind = designTime ? "DesignTime" : "Runtime"; + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(sourceItem.FilePath, kind); - RazorSourceGeneratorEventSource.Log.RewriteTagHelpersStop(filePath); - return (projectEngine, filePath, codeDocument); - }) + // Add a generated suffix so tools, such as coverlet, consider the file to be generated + var hintName = GetIdentifierFromPath(sourceItem.RelativePhysicalPath) + ".g.cs"; - // next we do a second parse, along with the helpers, but check for idempotency. If the tag helpers used on the previous parse match, the compiler can skip re-computing them - .Combine(allTagHelpers) - .Select((pair, _) => - { + var projectEngine = GetGenerationProjectEngine(allTagHelpers, sourceItem, imports, razorSourceGeneratorOptions); - var ((projectEngine, filePath, document), allTagHelpers) = pair; - RazorSourceGeneratorEventSource.Log.CheckAndRewriteTagHelpersStart(filePath); + var codeDocument = designTime + ? projectEngine.ProcessDesignTime(sourceItem) + : projectEngine.Process(sourceItem); - document = projectEngine.ProcessTagHelpers(document, allTagHelpers, checkForIdempotency: true); - - RazorSourceGeneratorEventSource.Log.CheckAndRewriteTagHelpersStop(filePath); - return (projectEngine, filePath, document); - }) + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(sourceItem.FilePath, kind); + return (hintName, codeDocument); + }); - .Select((pair, _) => + var csharpDocuments = codeDocuments(designTime: false) + .Select(static (tuple, _) => { - var (projectEngine, filePath, document) = pair; - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(filePath); - document = projectEngine.ProcessRemaining(document); - var csharpDocument = document.CodeDocument.GetCSharpDocument(); + var (hintName, codeDocument) = tuple; + + var csharpDocument = codeDocument.GetCSharpDocument(); - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(filePath); - return (filePath, csharpDocument); + return (hintName, csharpDocument); }) .WithLambdaComparer(static (a, b) => { @@ -288,13 +249,9 @@ 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 (filePath, csharpDocument) = pair; - - // Add a generated suffix so tools, such as coverlet, consider the file to be generated - var hintName = GetIdentifierFromPath(filePath) + ".g.cs"; - + var (hintName, csharpDocument) = pair; RazorSourceGeneratorEventSource.Log.AddSyntaxTrees(hintName); for (var i = 0; i < csharpDocument.Diagnostics.Count; i++) { @@ -305,6 +262,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.AddSource(hintName, csharpDocument.GeneratedCode); }); + + var hostOutput = codeDocuments(designTime: true); + context.RegisterHostOutput(hostOutput, static (context, tuple, _) => + { + var (hintName, codeDocument) = tuple; + context.AddOutput(hintName + ".rsg-cs", codeDocument.GetCSharpDocument().GeneratedCode); + context.AddOutput(hintName + ".rsg-html", codeDocument.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 b94646cc81a..1b7781289ea 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)] From b63d6515de5d5b1a3961826a0c3deb48ecbbdcaf Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 21 Mar 2023 10:32:53 -0700 Subject: [PATCH 02/22] WIP --- Directory.Build.props | 13 ++++ ...ore.Mvc.Razor.Extensions.Version1_X.csproj | 5 +- ...ore.Mvc.Razor.Extensions.Version2_X.csproj | 5 +- ...oft.AspNetCore.Mvc.Razor.Extensions.csproj | 5 +- .../src/Microsoft.CodeAnalysis.Razor.csproj | 8 ++- ...soft.NET.Sdk.Razor.SourceGenerators.csproj | 3 + .../RazorSourceGenerator.RazorProviders.cs | 6 +- ...NetCore.Razor.LanguageServer.Common.csproj | 10 +++ ...RazorLanguageServerCustomMessageTargets.cs | 2 + .../DelegatedTypes.cs | 3 + .../DefaultProjectSnapshotManagerAccessor.cs | 70 ++++++++++++++++++- ...oft.AspNetCore.Razor.LanguageServer.csproj | 6 ++ ...osoft.CodeAnalysis.Razor.Workspaces.csproj | 2 +- .../DefaultProjectSnapshotManager.cs | 9 +++ .../ProjectSystem/DocumentSnapshot.cs | 10 +++ .../ProjectSystem/DocumentState.cs | 10 +++ .../ProjectSystem/EphemeralProjectSnapshot.cs | 7 ++ .../GeneratorSnapshotProvider.cs | 31 ++++++++ .../ProjectSystem/IDocumentSnapshot.cs | 1 + .../ProjectSystem/IProjectSnapshot.cs | 4 ++ .../ProjectSystem/ImportDocumentSnapshot.cs | 5 ++ .../ProjectSystem/ProjectSnapshot.cs | 52 ++++++++++++++ .../ProjectSystem/ProjectState.cs | 4 ++ .../RazorServiceBase.cs | 5 ++ ...Microsoft.VisualStudio.Editor.Razor.csproj | 10 +++ ...tRazorLanguageServerCustomMessageTarget.cs | 32 +++++++++ .../RazorLanguageServerClient.cs | 19 +++++ .../RazorLanguageServerCustomMessageTarget.cs | 4 ++ 28 files changed, 331 insertions(+), 10 deletions(-) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs diff --git a/Directory.Build.props b/Directory.Build.props index 47f086bd447..95c16199319 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -52,6 +52,19 @@ to Razor tooling. --> + + + + + + + + + + + + + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj index 7cc35375bc1..0ba7e7de568 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core design time hosting infrastructure for the Razor view engine. @@ -22,6 +22,9 @@ + + + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj index 7cc35375bc1..562cf5c4010 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core design time hosting infrastructure for the Razor view engine. @@ -22,6 +22,9 @@ + + + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj index a5811d1a388..67b5084793b 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core design time hosting infrastructure for the Razor view engine. @@ -19,6 +19,9 @@ + + + diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj b/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj index 5eb6ea9feab..78d1ca8e115 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj @@ -1,4 +1,4 @@ - + Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor design-time infrastructure. @@ -10,6 +10,10 @@ - + + + + + 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 33b24c31967..4a22e493bef 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 @@ -24,6 +24,9 @@ + + + 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..6bcd532c45c 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs @@ -24,8 +24,10 @@ public partial class RazorSourceGenerator /// private static bool GetSuppressionStatus(AnalyzerConfigOptionsProvider optionsProvider, CancellationToken _) { - return optionsProvider.GlobalOptions.TryGetValue("build_property.SuppressRazorSourceGenerator", out var suppressRazorSourceGenerator) - && suppressRazorSourceGenerator == "true"; + return false; + + //return optionsProvider.GlobalOptions.TryGetValue("build_property.SuppressRazorSourceGenerator", out var suppressRazorSourceGenerator) + // && suppressRazorSourceGenerator == "true"; } private static (RazorSourceGenerationOptions?, Diagnostic?) ComputeRazorSourceGeneratorOptions((AnalyzerConfigOptionsProvider, ParseOptions) pair, CancellationToken ct) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj index 655a46af1ff..cab90a78020 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj @@ -12,6 +12,16 @@ + + + + + + + + + + diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs index 7999c141fb7..d2dc5422d14 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs @@ -50,4 +50,6 @@ internal static class RazorLanguageServerCustomMessageTargets public const string RazorPullDiagnosticEndpointName = "razor/pullDiagnostics"; public const string RazorReferencesEndpointName = "razor/references"; + + public const string RazorHostOutputsEndpointName = "razor/getHostOutput"; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs index 2bad9193c14..a95a7bf84c6 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Protocol/DelegatedTypes.cs @@ -50,3 +50,6 @@ internal record DelegatedCompletionItemResolveParams( VersionedTextDocumentIdentifier HostDocument, VSInternalCompletionItem CompletionItem, RazorLanguageKind OriginatingKind); + +internal record DelegatedGetHostOutputParams( + VersionedTextDocumentIdentifier HostDocument); diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs index 8b2a789aa0c..76b6f63ad01 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs @@ -3,18 +3,77 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.Extensions.Options; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Remote; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; +using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.RpcContracts.Documents; namespace Microsoft.AspNetCore.Razor.LanguageServer; + +//internal class SnapshotHandler : IRazorRequestHandler +//{ +// public bool MutatesSolutionState => false; + +// public TextDocumentIdentifier GetTextDocumentIdentifier(GetHostOutputRequest request) +// { +// throw new NotImplementedException(); +// } + +// public Task HandleRequestAsync(GetHostOutputRequest request, RazorRequestContext context, CancellationToken cancellationToken) +// { +// throw new NotImplementedException(); +// } +//} + + + +internal class LspHostOutput : IGeneratorSnapshotProvider +{ + ClientNotifierServiceBase _notifier; + + public LspHostOutput(ClientNotifierServiceBase notifier) + { + _notifier = notifier; + } + + public async Task GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) + { + // ask the LSP for the generated doc based on file path (and other stuff) + var request = new GetHostOutputRequest() + { + TextDocument = new TextDocumentIdentifier() + { + Uri = new UriBuilder() + { + Scheme = Uri.UriSchemeFile, + Path = documentSnapshot.FilePath, + Host = string.Empty, + }.Uri + } + }; + + var response = await _notifier.SendRequestAsync(RazorLanguageServerCustomMessageTargets.RazorHostOutputsEndpointName, request, CancellationToken.None); + //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Response to get gen was:" + response.Response); + } +} + internal class DefaultProjectSnapshotManagerAccessor : ProjectSnapshotManagerAccessor, IDisposable { private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher; private readonly IEnumerable _changeTriggers; private readonly IOptionsMonitor _optionsMonitor; private readonly AdhocWorkspaceFactory _workspaceFactory; + private readonly ClientNotifierServiceBase _notifierService; private ProjectSnapshotManagerBase? _instance; private bool _disposed; @@ -22,7 +81,8 @@ public DefaultProjectSnapshotManagerAccessor( ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, IEnumerable changeTriggers, IOptionsMonitor optionsMonitor, - AdhocWorkspaceFactory workspaceFactory) + AdhocWorkspaceFactory workspaceFactory, + ClientNotifierServiceBase notifier) { if (projectSnapshotManagerDispatcher is null) { @@ -48,6 +108,10 @@ public DefaultProjectSnapshotManagerAccessor( _changeTriggers = changeTriggers; _optionsMonitor = optionsMonitor; _workspaceFactory = workspaceFactory; + _notifierService = notifier; + //_generatorSnapshotFactory = snapshotFactory; + + Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("project accessor made. notifier is :" + (notifier is not null)); } public override ProjectSnapshotManagerBase Instance @@ -57,9 +121,11 @@ public override ProjectSnapshotManagerBase Instance if (_instance is null) { var workspace = _workspaceFactory.Create( - workspaceServices: new[] + workspaceServices: new IWorkspaceService[] { + //PROTOTYPE: it's here we could inject a 'host outputs retrieval' service new RemoteProjectSnapshotProjectEngineFactory(_optionsMonitor) + , new LspHostOutput(_notifierService) }); _instance = new DefaultProjectSnapshotManager( _projectSnapshotManagerDispatcher, diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj index a4681be5cbd..eab4d8de4b9 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj @@ -42,4 +42,10 @@ + + + True + + + diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj index c6331c63437..f6fe419790a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj @@ -15,7 +15,7 @@ - + 6.0.0 - 6.0.0 - 6.0.0 + 7.0.0 + 7.0.0 6.0.0 6.0.0 6.0.0 diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj b/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj index 78d1ca8e115..198a293d099 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj @@ -10,10 +10,5 @@ - - - - - diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs index d2dc5422d14..68f745b8739 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/RazorLanguageServerCustomMessageTargets.cs @@ -51,5 +51,5 @@ internal static class RazorLanguageServerCustomMessageTargets public const string RazorReferencesEndpointName = "razor/references"; - public const string RazorHostOutputsEndpointName = "razor/getHostOutput"; + public const string RazorHostOutputsEndpointName = "razor/hostOutput"; } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs index 8a45fc757ff..2c0057b41fe 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs @@ -35,27 +35,31 @@ public LspHostOutput(ClientNotifierServiceBase notifier) var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("/")); var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? "") + ".g.cs"; - var request = new GetHostOutputRequest() + var csharp = await RequestOutput(documentName + ".rsg-cs"); + var html = await RequestOutput(documentName + ".rsg-html"); + + return (csharp, html); + + async Task RequestOutput(string name) { - TextDocument = new TextDocumentIdentifier() + var request = new HostOutputRequest() { - Uri = new UriBuilder() + TextDocument = new TextDocumentIdentifier() { - Scheme = Uri.UriSchemeFile, - Path = documentSnapshot.FilePath, - Host = string.Empty, - }.Uri - }, - GeneratorName = "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", - RequestedOutputs = new[] - { - documentName + ".rsg-cs", - documentName + ".rsg-html", - } - }; - - var response = await _notifier.SendRequestAsync(RazorLanguageServerCustomMessageTargets.RazorHostOutputsEndpointName, request, CancellationToken.None); - return (response.Outputs[0] ?? string.Empty, response.Outputs[1] ?? string.Empty); //TODO: do this on the roslyn side? + Uri = new UriBuilder() + { + Scheme = Uri.UriSchemeFile, + Path = documentSnapshot.FilePath, + Host = string.Empty, + }.Uri + }, + GeneratorName = "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", + RequestedOutput = name, + }; + + var response = await _notifier.SendRequestAsync(RazorLanguageServerCustomMessageTargets.RazorHostOutputsEndpointName, request, CancellationToken.None); + return response.Output ?? string.Empty; + } } //copied from the generator @@ -126,7 +130,7 @@ public DefaultProjectSnapshotManagerAccessor( _notifierService = notifier; //_generatorSnapshotFactory = snapshotFactory; - Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("project accessor made. notifier is :" + (notifier is not null)); + //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("project accessor made. notifier is :" + (notifier is not null)); } public override ProjectSnapshotManagerBase Instance diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj index f6fe419790a..c6331c63437 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj @@ -15,7 +15,7 @@ - 1.1.2-beta1.22512.1 17.5.0-preview-2-33117-317 - 17.5.274-preview + 17.6.76-preview 4.6.0-3.23164.6 17.6.15-preview 4.6.0-2.23128.3 diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.Helpers.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.Helpers.cs index 7dfece6c432..958453f526b 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.Helpers.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.Helpers.cs @@ -114,5 +114,58 @@ private static SourceGeneratorProjectEngine GetGenerationProjectEngine( return new SourceGeneratorProjectEngine(projectEngine); } - } + + private static RazorProjectEngine GetDesignTimeProjectEngine( + IReadOnlyList tagHelpers, + SourceGeneratorProjectItem item, + IEnumerable imports, + RazorSourceGenerationOptions razorSourceGeneratorOptions) + { + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(item); + foreach (var import in imports) + { + fileSystem.Add(import); + } + + var projectEngine = RazorProjectEngine.Create(razorSourceGeneratorOptions.Configuration, fileSystem, b => + { + b.Features.Add(new DefaultTypeNameFeature()); + b.SetRootNamespace(razorSourceGeneratorOptions.RootNamespace); + + b.Features.Add(new ConfigureRazorCodeGenerationOptions(options => + { + options.SuppressMetadataSourceChecksumAttributes = !razorSourceGeneratorOptions.GenerateMetadataSourceChecksumAttributes; + options.SupportLocalizedComponentNames = razorSourceGeneratorOptions.SupportLocalizedComponentNames; + })); + + b.Features.Add(new StaticTagHelperFeature { TagHelpers = tagHelpers }); + b.Features.Add(new DefaultTagHelperDescriptorProvider()); + + CompilerFeatures.Register(b); + RazorExtensions.Register(b); + + b.SetCSharpLanguageVersion(razorSourceGeneratorOptions.CSharpLanguageVersion); + }); + + return projectEngine; + } + } + + internal sealed class StaticTagHelperFeature : RazorEngineFeatureBase, ITagHelperFeature + { + public IReadOnlyList TagHelpers { get; set; } + + public IReadOnlyList GetDescriptors() => TagHelpers; + + public StaticTagHelperFeature() + { + TagHelpers = new List(); + } + + public StaticTagHelperFeature(IEnumerable tagHelpers) + { + TagHelpers = new List(tagHelpers); + } + } } diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs index 199e1e91c27..8873712e9bd 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs @@ -2,266 +2,310 @@ // 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.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler; namespace Microsoft.NET.Sdk.Razor.SourceGenerators { - [Generator] - public partial class RazorSourceGenerator : IIncrementalGenerator - { - private static RazorSourceGeneratorEventSource Log => RazorSourceGeneratorEventSource.Log; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var analyzerConfigOptions = context.AnalyzerConfigOptionsProvider; - var parseOptions = context.ParseOptionsProvider; - var compilation = context.CompilationProvider; - - // determine if we should suppress this run and filter out all the additional files if so - var isGeneratorSuppressed = analyzerConfigOptions.Select(GetSuppressionStatus); - var additionalTexts = context.AdditionalTextsProvider - .Combine(isGeneratorSuppressed) - .Where(pair => !pair.Right) - .Select((pair, _) => pair.Left); - - var razorSourceGeneratorOptions = analyzerConfigOptions - .Combine(parseOptions) - .Select(ComputeRazorSourceGeneratorOptions) - .ReportDiagnostics(context); - - var sourceItems = additionalTexts - .Where(static (file) => file.Path.EndsWith(".razor", StringComparison.OrdinalIgnoreCase) || file.Path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) - .Combine(analyzerConfigOptions) - .Select(ComputeProjectItems) - .ReportDiagnostics(context); - - var hasRazorFiles = sourceItems.Collect() - .Select(static (sourceItems, _) => sourceItems.Any()); - - var importFiles = sourceItems.Where(static file => - { - var path = file.FilePath; - if (path.EndsWith(".razor", StringComparison.OrdinalIgnoreCase)) - { - var fileName = Path.GetFileNameWithoutExtension(path); - return string.Equals(fileName, "_Imports", StringComparison.OrdinalIgnoreCase); - } - else if (path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) - { - var fileName = Path.GetFileNameWithoutExtension(path); - return string.Equals(fileName, "_ViewImports", StringComparison.OrdinalIgnoreCase); - } - - return false; - }); - - var componentFiles = sourceItems.Where(static file => file.FilePath.EndsWith(".razor", StringComparison.OrdinalIgnoreCase)); - - var generatedDeclarationCode = componentFiles - .Combine(importFiles.Collect()) - .Combine(razorSourceGeneratorOptions) - .Select(static (pair, _) => - { - var ((sourceItem, importFiles), razorSourceGeneratorOptions) = pair; - RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.FilePath); - - var projectEngine = GetDeclarationProjectEngine(sourceItem, importFiles, razorSourceGeneratorOptions); - - var codeGen = projectEngine.Process(sourceItem); - - var result = codeGen.GetCSharpDocument().GeneratedCode; - - RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStop(sourceItem.FilePath); - - return result; - }); - - var generatedDeclarationSyntaxTrees = generatedDeclarationCode - .Combine(parseOptions) - .Select(static (pair, _) => - { - var (generatedDeclarationCode, parseOptions) = pair; - return CSharpSyntaxTree.ParseText(generatedDeclarationCode, (CSharpParseOptions)parseOptions); - }); - - var tagHelpersFromCompilation = compilation - .Combine(generatedDeclarationSyntaxTrees.Collect()) - .Combine(razorSourceGeneratorOptions) - .Select(static (pair, _) => - { - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStart(); - - var ((compilation, generatedDeclarationSyntaxTrees), razorSourceGeneratorOptions) = pair; - - var tagHelperFeature = new StaticCompilationTagHelperFeature(); - var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); - - var compilationWithDeclarations = compilation.AddSyntaxTrees(generatedDeclarationSyntaxTrees); - - tagHelperFeature.Compilation = compilationWithDeclarations; - tagHelperFeature.TargetSymbol = compilationWithDeclarations.Assembly; - - var result = (IList)tagHelperFeature.GetDescriptors(); - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStop(); - return result; - }) - .WithLambdaComparer(static (a, b) => - { - if (a.Count != b.Count) - { - return false; - } - - for (var i = 0; i < a.Count; i++) - { - if (!a[i].Equals(b[i])) - { - return false; - } - } - - return true; - }, getHashCode: static a => a.Count); - - var tagHelpersFromReferences = compilation - .Combine(razorSourceGeneratorOptions) - .Combine(hasRazorFiles) - .WithLambdaComparer(static (a, b) => - { - var ((compilationA, razorSourceGeneratorOptionsA), hasRazorFilesA) = a; - var ((compilationB, razorSourceGeneratorOptionsB), hasRazorFilesB) = b; - - if (!compilationA.References.SequenceEqual(compilationB.References)) - { - return false; - } - - if (razorSourceGeneratorOptionsA != razorSourceGeneratorOptionsB) - { - return false; - } - - return hasRazorFilesA == hasRazorFilesB; - }, - static item => - { - // we'll use the number of references as a hashcode. - var ((compilationA, razorSourceGeneratorOptionsA), hasRazorFilesA) = item; - return compilationA.References.GetHashCode(); - }) - .Select(static (pair, _) => - { - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStart(); - - var ((compilation, razorSourceGeneratorOptions), hasRazorFiles) = pair; - if (!hasRazorFiles) - { - // If there's no razor code in this app, don't do anything. - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStop(); - return ImmutableArray.Empty; - } - - var tagHelperFeature = new StaticCompilationTagHelperFeature(); - var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); - - List descriptors = new(); - tagHelperFeature.Compilation = compilation; - foreach (var reference in compilation.References) - { - if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly) - { - tagHelperFeature.TargetSymbol = assembly; - descriptors.AddRange(tagHelperFeature.GetDescriptors()); - } - } - - RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStop(); - return (ICollection)descriptors; - }); - - var allTagHelpers = tagHelpersFromCompilation - .Combine(tagHelpersFromReferences) - .Select(static (pair, _) => - { - var (tagHelpersFromCompilation, tagHelpersFromReferences) = pair; - var count = tagHelpersFromCompilation.Count + tagHelpersFromReferences.Count; - if (count == 0) - { - return Array.Empty(); - } - - var allTagHelpers = new TagHelperDescriptor[count]; - tagHelpersFromCompilation.CopyTo(allTagHelpers, 0); - tagHelpersFromReferences.CopyTo(allTagHelpers, tagHelpersFromCompilation.Count); - - return allTagHelpers; - }); - - - IncrementalValuesProvider<(string hintName, RazorCodeDocument codeDocument)> codeDocuments(bool designTime) => sourceItems - .Combine(importFiles.Collect()) - .Combine(allTagHelpers) - .Combine(razorSourceGeneratorOptions) - .Select((pair, _) => - { - var (((sourceItem, imports), allTagHelpers), razorSourceGeneratorOptions) = pair; - - var kind = designTime ? "DesignTime" : "Runtime"; - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(sourceItem.FilePath, kind); - - // Add a generated suffix so tools, such as coverlet, consider the file to be generated - var hintName = GetIdentifierFromPath(sourceItem.RelativePhysicalPath) + ".g.cs"; - - var projectEngine = GetGenerationProjectEngine(allTagHelpers, sourceItem, imports, razorSourceGeneratorOptions); - - var codeDocument = designTime - ? projectEngine.ProcessDesignTime(sourceItem) - : projectEngine.Process(sourceItem); - - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(sourceItem.FilePath, kind); - return (hintName, codeDocument); - }); - - var csharpDocuments = codeDocuments(designTime: false) - .Select(static (tuple, _) => - { - var (hintName, codeDocument) = tuple; - - var csharpDocument = codeDocument.GetCSharpDocument(); - - return (hintName, csharpDocument); - }) - .WithLambdaComparer(static (a, b) => - { - if (a.csharpDocument.Diagnostics.Count > 0 || b.csharpDocument.Diagnostics.Count > 0) - { - // if there are any diagnostics, treat the documents as unequal and force RegisterSourceOutput to be called uncached. - return false; - } - - return string.Equals(a.csharpDocument.GeneratedCode, b.csharpDocument.GeneratedCode, StringComparison.Ordinal); - }, static a => StringComparer.Ordinal.GetHashCode(a.csharpDocument)); - - context.RegisterImplementationSourceOutput(csharpDocuments, static (context, pair) => - { - var (hintName, csharpDocument) = pair; - RazorSourceGeneratorEventSource.Log.AddSyntaxTrees(hintName); - for (var i = 0; i < csharpDocument.Diagnostics.Count; i++) - { - var razorDiagnostic = csharpDocument.Diagnostics[i]; - var csharpDiagnostic = razorDiagnostic.AsDiagnostic(); - context.ReportDiagnostic(csharpDiagnostic); - } - - context.AddSource(hintName, csharpDocument.GeneratedCode); - }); + [Generator] + public partial class RazorSourceGenerator : IIncrementalGenerator + { + private static RazorSourceGeneratorEventSource Log => RazorSourceGeneratorEventSource.Log; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var analyzerConfigOptions = context.AnalyzerConfigOptionsProvider; + var parseOptions = context.ParseOptionsProvider; + 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 additionalTexts = context.AdditionalTextsProvider + .Combine(isGeneratorSuppressed) + .Where(pair => !pair.Right) + .Select((pair, _) => pair.Left); + + var razorSourceGeneratorOptions = analyzerConfigOptions + .Combine(parseOptions) + .Select(ComputeRazorSourceGeneratorOptions) + .ReportDiagnostics(context); + + var sourceItems = additionalTexts + .Where(static (file) => file.Path.EndsWith(".razor", StringComparison.OrdinalIgnoreCase) || file.Path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) + .Combine(analyzerConfigOptions) + .Select(ComputeProjectItems) + .ReportDiagnostics(context); + + var hasRazorFiles = sourceItems.Collect() + .Select(static (sourceItems, _) => sourceItems.Any()); + + var importFiles = sourceItems.Where(static file => + { + var path = file.FilePath; + if (path.EndsWith(".razor", StringComparison.OrdinalIgnoreCase)) + { + var fileName = Path.GetFileNameWithoutExtension(path); + return string.Equals(fileName, "_Imports", StringComparison.OrdinalIgnoreCase); + } + else if (path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) + { + var fileName = Path.GetFileNameWithoutExtension(path); + return string.Equals(fileName, "_ViewImports", StringComparison.OrdinalIgnoreCase); + } + + return false; + }); + + var componentFiles = sourceItems.Where(static file => file.FilePath.EndsWith(".razor", StringComparison.OrdinalIgnoreCase)); + + var generatedDeclarationCode = componentFiles + .Combine(importFiles.Collect()) + .Combine(razorSourceGeneratorOptions) + .WithLambdaComparer((old, @new) => (old.Right.Equals(@new.Right) && old.Left.Left.Equals(@new.Left.Left) && old.Left.Right.SequenceEqual(@new.Left.Right)), (a) => a.GetHashCode()) + .Select(static (pair, _) => + { + + var ((sourceItem, importFiles), razorSourceGeneratorOptions) = pair; + RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.RelativePhysicalPath); + + var projectEngine = GetDeclarationProjectEngine(sourceItem, importFiles, razorSourceGeneratorOptions); + + var codeGen = projectEngine.Process(sourceItem); + + var result = codeGen.GetCSharpDocument().GeneratedCode; + + RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStop(sourceItem.RelativePhysicalPath); + + return (result, sourceItem.RelativePhysicalPath); + }); + + var generatedDeclarationSyntaxTrees = generatedDeclarationCode + .Combine(parseOptions) + .Select(static (pair, ct) => + { + var ((generatedDeclarationCode, filePath), parseOptions) = pair; + return CSharpSyntaxTree.ParseText(generatedDeclarationCode, (CSharpParseOptions)parseOptions, filePath, cancellationToken: ct); + }); + + var tagHelpersFromComponents = generatedDeclarationSyntaxTrees + .Combine(compilation) + .Combine(razorSourceGeneratorOptions) + .SelectMany(static (pair, ct) => + { + + var ((generatedDeclarationSyntaxTree, compilation), razorSourceGeneratorOptions) = pair; + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromComponentStart(generatedDeclarationSyntaxTree.FilePath); + + var tagHelperFeature = new StaticCompilationTagHelperFeature(); + var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); + + var compilationWithDeclarations = compilation.AddSyntaxTrees(generatedDeclarationSyntaxTree); + + // try and find the specific root class this component is declaring, falling back to the assembly if for any reason the code is not in the shape we expect + ISymbol targetSymbol = compilationWithDeclarations.Assembly; + var root = generatedDeclarationSyntaxTree.GetRoot(ct); + if (root is CompilationUnitSyntax { Members: [NamespaceDeclarationSyntax { Members: [ClassDeclarationSyntax classSyntax, ..] }, ..] }) + { + var declaredClass = compilationWithDeclarations.GetSemanticModel(generatedDeclarationSyntaxTree).GetDeclaredSymbol(classSyntax, ct); + Debug.Assert(declaredClass is null || declaredClass is { AllInterfaces: [{ Name: "IComponent" }, ..] }); + targetSymbol = declaredClass ?? targetSymbol; + } + + tagHelperFeature.Compilation = compilationWithDeclarations; + tagHelperFeature.TargetSymbol = targetSymbol; + + var result = tagHelperFeature.GetDescriptors(); + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromComponentStop(generatedDeclarationSyntaxTree.FilePath); + return result; + }); + + var tagHelpersFromCompilation = compilation + .Combine(razorSourceGeneratorOptions) + .Select(static (pair, _) => + { + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStart(); + + var (compilation, razorSourceGeneratorOptions) = pair; + + var tagHelperFeature = new StaticCompilationTagHelperFeature(); + var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); + + tagHelperFeature.Compilation = compilation; + tagHelperFeature.TargetSymbol = compilation.Assembly; + + var result = tagHelperFeature.GetDescriptors(); + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStop(); + return result; + }); + + var tagHelpersFromReferences = compilation + .Combine(razorSourceGeneratorOptions) + .Combine(hasRazorFiles) + .WithLambdaComparer(static (a, b) => + { + var ((compilationA, razorSourceGeneratorOptionsA), hasRazorFilesA) = a; + var ((compilationB, razorSourceGeneratorOptionsB), hasRazorFilesB) = b; + + if (!compilationA.References.SequenceEqual(compilationB.References)) + { + return false; + } + + if (razorSourceGeneratorOptionsA != razorSourceGeneratorOptionsB) + { + return false; + } + + return hasRazorFilesA == hasRazorFilesB; + }, + static item => + { + // we'll use the number of references as a hashcode. + var ((compilationA, razorSourceGeneratorOptionsA), hasRazorFilesA) = item; + return compilationA.References.GetHashCode(); + }) + .Select(static (pair, _) => + { + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStart(); + + var ((compilation, razorSourceGeneratorOptions), hasRazorFiles) = pair; + if (!hasRazorFiles) + { + // If there's no razor code in this app, don't do anything. + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStop(); + return ImmutableArray.Empty; + } + + var tagHelperFeature = new StaticCompilationTagHelperFeature(); + var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); + + using var pool = ArrayBuilderPool.GetPooledObject(out var descriptors); + tagHelperFeature.Compilation = compilation; + foreach (var reference in compilation.References) + { + if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly) + { + tagHelperFeature.TargetSymbol = assembly; + descriptors.AddRange(tagHelperFeature.GetDescriptors()); + } + } + + RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStop(); + return descriptors.ToImmutable(); + }); + + var allTagHelpers = tagHelpersFromComponents.Collect() + .Combine(tagHelpersFromCompilation) + .Combine(tagHelpersFromReferences) + .Select(static (pair, _) => + { + var ((tagHelpersFromComponents, tagHelpersFromCompilation), tagHelpersFromReferences) = pair; + var count = tagHelpersFromCompilation.Length + tagHelpersFromReferences.Length + tagHelpersFromComponents.Length; + if (count == 0) + { + return ImmutableArray.Empty; + } + + using var pool = ArrayBuilderPool.GetPooledObject(out var allTagHelpers); + allTagHelpers.AddRange(tagHelpersFromCompilation); + allTagHelpers.AddRange(tagHelpersFromReferences); + allTagHelpers.AddRange(tagHelpersFromComponents); + + return allTagHelpers.ToImmutable(); + }); + + var generatedOutput = sourceItems + .Combine(importFiles.Collect()) + .WithLambdaComparer((old, @new) => old.Left.Equals(@new.Left) && old.Right.SequenceEqual(@new.Right), (a) => a.GetHashCode()) + .Combine(razorSourceGeneratorOptions) + .Select(static (pair, _) => + { + var ((sourceItem, imports), razorSourceGeneratorOptions) = pair; + + RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStart(sourceItem.RelativePhysicalPath); + + var projectEngine = GetGenerationProjectEngine(sourceItem, imports, razorSourceGeneratorOptions); + + var document = projectEngine.ProcessInitialParse(sourceItem); + + RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStop(sourceItem.RelativePhysicalPath); + return (projectEngine, sourceItem.RelativePhysicalPath, document); + }) + + // Add the tag helpers in, but ignore if they've changed or not, only reprocessing the actual document changed + .Combine(allTagHelpers) + .WithLambdaComparer((old, @new) => old.Left.Equals(@new.Left), (item) => item.GetHashCode()) + .Select((pair, _) => + { + var ((projectEngine, filePath, codeDocument), allTagHelpers) = pair; + RazorSourceGeneratorEventSource.Log.RewriteTagHelpersStart(filePath); + + codeDocument = projectEngine.ProcessTagHelpers(codeDocument, allTagHelpers, checkForIdempotency: false); + + RazorSourceGeneratorEventSource.Log.RewriteTagHelpersStop(filePath); + return (projectEngine, filePath, codeDocument); + }) + + // next we do a second parse, along with the helpers, but check for idempotency. If the tag helpers used on the previous parse match, the compiler can skip re-computing them + .Combine(allTagHelpers) + .Select((pair, _) => + { + + var ((projectEngine, filePath, document), allTagHelpers) = pair; + RazorSourceGeneratorEventSource.Log.CheckAndRewriteTagHelpersStart(filePath); + + document = projectEngine.ProcessTagHelpers(document, allTagHelpers, checkForIdempotency: true); + + RazorSourceGeneratorEventSource.Log.CheckAndRewriteTagHelpersStop(filePath); + return (projectEngine, filePath, document); + }) + + .Select((pair, _) => + { + var (projectEngine, filePath, document) = pair; + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(filePath, "Runtime"); + document = projectEngine.ProcessRemaining(document); + var csharpDocument = document.CodeDocument.GetCSharpDocument(); + + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(filePath, "Runtime"); + return (filePath, csharpDocument); + }) + .WithLambdaComparer(static (a, b) => + { + if (a.csharpDocument.Diagnostics.Count > 0 || b.csharpDocument.Diagnostics.Count > 0) + { + // if there are any diagnostics, treat the documents as unequal and force RegisterSourceOutput to be called uncached. + return false; + } + + return string.Equals(a.csharpDocument.GeneratedCode, b.csharpDocument.GeneratedCode, StringComparison.Ordinal); + }, static a => StringComparer.Ordinal.GetHashCode(a.csharpDocument)); + + context.RegisterSourceOutput(generatedOutput, static (context, 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++) + { + var razorDiagnostic = csharpDocument.Diagnostics[i]; + var csharpDiagnostic = razorDiagnostic.AsDiagnostic(); + context.ReportDiagnostic(csharpDiagnostic); + } + + context.AddSource(hintName, csharpDocument.GeneratedCode); + }); context.RegisterHostOutput(processed(designTime: true), static (context, pair, _) => { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs index 2c0057b41fe..0543fd87d16 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs @@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer; - internal class LspHostOutput : IGeneratorSnapshotProvider { ClientNotifierServiceBase _notifier; @@ -63,7 +62,7 @@ async Task RequestOutput(string name) } //copied from the generator - private static string GetIdentifierFromPath(string filePath) + internal static string GetIdentifierFromPath(string filePath) { var builder = new StringBuilder(filePath.Length); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index 03085cda2d5..0c2c2094310 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -503,9 +503,21 @@ static void PropagateToTaskCompletionSource( // actually, is that true? Surely we'd create the snapshot with the data already generated? No, maybe not. Hmm. Argh, not sure. // lets see what it looks like we if were to ask the project for the data here. + + + // So we have the source already, no need to serialize that. Then we have the codeDocument which is a layer over the top of the source doc + // The code doc has the imports, the source doc, the tag helpers, the file kind, the CSharp doc, the html doc, the options, the diagnostics and the source mappings + + // Tag helpers are interesting, because we have a copy of them here already. So might not need to serialize those + // The chsarp and html docs are easy + // The options are simple value pairs, so should be easy + // Diagnostics are... complex? Ok the diagnostic descriptors are slightly hard, because they have a callback to get the resource, but we should be able to just 'flatten' them on the IDE side as the languages will be the same. + // Source mappings are *shrug*? + // Huh, plus there is everything in the items collection which is... a lot. (SyntaxTrees etc) + var codeDoc2 = await project.GetGeneratedDocumentsAsync(document); - RazorCodeDocument codeDocument; + RazorCodeDocument? codeDocument = null; if (codeDoc2.CSharp != "") { codeDocument = RazorCodeDocument.Create(documentSource, importSources); @@ -522,7 +534,7 @@ static void PropagateToTaskCompletionSource( } else { - codeDocument = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); + //codeDocument = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Generated document locally"); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs index dca23118d7e..cb9ec332ea8 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Remote; namespace Microsoft.CodeAnalysis.Razor.Workspaces; @@ -18,14 +19,59 @@ internal class DeelyFactory : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - return new RazorInProcGeneratorSnapshotDeely(); + //ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor = workspaceServices.GetRequiredService(workspaceServices); + return new RazorInProcGeneratorSnapshotDeely(workspaceServices.Workspace); } } internal class RazorInProcGeneratorSnapshotDeely : IGeneratorSnapshotProvider { - public Task<(string CSharp, string Html)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) + + private readonly Workspace _workspace; + + public RazorInProcGeneratorSnapshotDeely(Workspace workspace) { - return Task.FromResult(("abc", "def")); + _workspace = workspace; + } + + public async Task<(string CSharp, string Html)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) + { + string? csharp = null; + string? html = null; + + var project = _workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == documentSnapshot.Project.FilePath); + if (project is not null) + { + // PROTOTYPE: factor this out so we can share it + var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("\\")); + var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? "") + ".g.cs"; + + csharp = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg-cs", System.Threading.CancellationToken.None); + html = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg-html", System.Threading.CancellationToken.None); + } + + return (csharp ?? "", html ?? ""); + } + + // PROTOTYPE: copied from the generator + internal static string GetIdentifierFromPath(string filePath) + { + var builder = new StringBuilder(filePath.Length); + + for (var i = 0; i < filePath.Length; i++) + { + switch (filePath[i]) + { + case ':' or '\\' or '/': + case char ch when !char.IsLetterOrDigit(ch): + builder.Append('_'); + break; + default: + builder.Append(filePath[i]); + break; + } + } + + return builder.ToString(); } } From b56894d4daaba414d857ae84b9d3d2f1a8eee4ec Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 24 Apr 2023 14:26:38 -0700 Subject: [PATCH 07/22] Fixup - Ignore --- .../RazorSourceGenerator.cs | 58 ++--- .../SourceGeneratorProjectEngine.cs | 204 +++++++++--------- .../RazorSourceGeneratorTests.cs | 48 ++--- .../RazorSourceGeneratorTestsBase.cs | 59 ++++- 4 files changed, 213 insertions(+), 156 deletions(-) diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs index 8873712e9bd..d4ea167c61d 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs @@ -27,11 +27,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) @@ -72,7 +72,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .WithLambdaComparer((old, @new) => (old.Right.Equals(@new.Right) && old.Left.Left.Equals(@new.Left.Left) && old.Left.Right.SequenceEqual(@new.Left.Right)), (a) => a.GetHashCode()) .Select(static (pair, _) => { - var ((sourceItem, importFiles), razorSourceGeneratorOptions) = pair; RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.RelativePhysicalPath); @@ -100,7 +99,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Combine(razorSourceGeneratorOptions) .SelectMany(static (pair, ct) => { - var ((generatedDeclarationSyntaxTree, compilation), razorSourceGeneratorOptions) = pair; RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromComponentStart(generatedDeclarationSyntaxTree.FilePath); @@ -222,11 +220,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return allTagHelpers.ToImmutable(); }); - var generatedOutput = sourceItems + var withOptions = sourceItems .Combine(importFiles.Collect()) .WithLambdaComparer((old, @new) => old.Left.Equals(@new.Left) && old.Right.SequenceEqual(@new.Right), (a) => a.GetHashCode()) - .Combine(razorSourceGeneratorOptions) - .Select(static (pair, _) => + .Combine(razorSourceGeneratorOptions); + + IncrementalValuesProvider<(string, SourceGeneratorRazorCodeDocument)> processed(bool designTime) => withOptions + .Select((pair, _) => { var ((sourceItem, imports), razorSourceGeneratorOptions) = pair; @@ -234,7 +234,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var projectEngine = GetGenerationProjectEngine(sourceItem, imports, razorSourceGeneratorOptions); - var document = projectEngine.ProcessInitialParse(sourceItem); + var document = projectEngine.ProcessInitialParse(sourceItem, designTime: designTime); RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStop(sourceItem.RelativePhysicalPath); return (projectEngine, sourceItem.RelativePhysicalPath, document); @@ -258,7 +258,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Combine(allTagHelpers) .Select((pair, _) => { - var ((projectEngine, filePath, document), allTagHelpers) = pair; RazorSourceGeneratorEventSource.Log.CheckAndRewriteTagHelpersStart(filePath); @@ -271,12 +270,20 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select((pair, _) => { var (projectEngine, filePath, document) = pair; - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(filePath, "Runtime"); + var kind = designTime ? "DesignTime" : "Runtime"; + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(filePath, kind); + document = projectEngine.ProcessRemaining(document); - var csharpDocument = document.CodeDocument.GetCSharpDocument(); - RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(filePath, "Runtime"); - return (filePath, csharpDocument); + RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(filePath, kind); + return (filePath, document); + }); + + var csharpDocuments = processed(designTime: false) + .Select(static (pair, _) => + { + var (filePath, document) = pair; + return (filePath, csharpDocument: document.CodeDocument.GetCSharpDocument()); }) .WithLambdaComparer(static (a, b) => { @@ -289,7 +296,7 @@ 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 (filePath, csharpDocument) = pair; @@ -307,14 +314,13 @@ 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.json", RazorCodeDocumentSerializer.Instance.Serialize(document.CodeDocument)); - context.AddOutput(hintName + ".rsg.cs", document.CodeDocument.GetCSharpDocument().GeneratedCode); - context.AddOutput(hintName + ".rsg.html", document.CodeDocument.GetHtmlDocument().GeneratedCode); - }); - } - } + context.RegisterHostOutput(processed(designTime: true), static (context, pair, _) => + { + var (filePath, document) = pair; + var hintName = GetIdentifierFromPath(filePath); + context.AddOutput(hintName + ".rsg.cs", document.CodeDocument.GetCSharpDocument().GeneratedCode); + context.AddOutput(hintName + ".rsg.html", document.CodeDocument.GetHtmlDocument().GeneratedCode); + }); + } + } } diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/SourceGeneratorProjectEngine.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/SourceGeneratorProjectEngine.cs index 4acb91159b3..791ab83f366 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/SourceGeneratorProjectEngine.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/SourceGeneratorProjectEngine.cs @@ -12,104 +12,106 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators; internal class SourceGeneratorProjectEngine : DefaultRazorProjectEngine { - private readonly int discoveryPhaseIndex = -1; - - private readonly int rewritePhaseIndex = -1; - - public SourceGeneratorProjectEngine(DefaultRazorProjectEngine projectEngine) - : base(projectEngine.Configuration, projectEngine.Engine, projectEngine.FileSystem, projectEngine.ProjectFeatures) - { - for (int i = 0; i < Engine.Phases.Count; i++) - { - if (Engine.Phases[i] is DefaultRazorTagHelperContextDiscoveryPhase) - { - discoveryPhaseIndex = i; - } - else if (Engine.Phases[i] is DefaultRazorTagHelperRewritePhase) - { - rewritePhaseIndex = i; - } - else if (discoveryPhaseIndex >= 0 && rewritePhaseIndex >= 0) - { - break; - } - } - Debug.Assert(discoveryPhaseIndex >= 0); - Debug.Assert(rewritePhaseIndex >= 0); - } - - public SourceGeneratorRazorCodeDocument ProcessInitialParse(RazorProjectItem projectItem) - { - var codeDocument = CreateCodeDocumentCore(projectItem); - ProcessPartial(codeDocument, 0, discoveryPhaseIndex); - - // record the syntax tree, before the tag helper re-writing occurs - codeDocument.SetPreTagHelperSyntaxTree(codeDocument.GetSyntaxTree()); - return new SourceGeneratorRazorCodeDocument(codeDocument); - } - - public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCodeDocument sgDocument, IReadOnlyList tagHelpers, bool checkForIdempotency) - { - Debug.Assert(sgDocument.CodeDocument.GetPreTagHelperSyntaxTree() is not null); - - int startIndex = discoveryPhaseIndex; - var codeDocument = sgDocument.CodeDocument; - var previousTagHelpers = codeDocument.GetTagHelpers(); - if (checkForIdempotency && previousTagHelpers is not null) - { - // compare the tag helpers with the ones the document last used - if (Enumerable.SequenceEqual(tagHelpers, previousTagHelpers)) - { - // tag helpers are the same, nothing to do! - return sgDocument; - } - else - { - // tag helpers have changed, figure out if we need to re-write - var oldContextHelpers = codeDocument.GetTagHelperContext().TagHelpers; - - // re-run the scope check to figure out which tag helpers this document can see - codeDocument.SetTagHelpers(tagHelpers); - Engine.Phases[discoveryPhaseIndex].Execute(codeDocument); - - // Check if any new tag helpers were added or ones we previously used were removed - var newContextHelpers = codeDocument.GetTagHelperContext().TagHelpers; - var added = newContextHelpers.Except(oldContextHelpers); - var referencedByRemoved = codeDocument.GetReferencedTagHelpers().Except(newContextHelpers); - if (!added.Any() && !referencedByRemoved.Any()) - { - // Either nothing new, or any that got removed weren't used by this document anyway - return sgDocument; - } - - // We need to re-write the document, but can skip the scoping as we just performed it - startIndex = rewritePhaseIndex; - } - } - else - { - codeDocument.SetTagHelpers(tagHelpers); - } - - ProcessPartial(codeDocument, startIndex, rewritePhaseIndex + 1); - return new SourceGeneratorRazorCodeDocument(codeDocument); - } - - public SourceGeneratorRazorCodeDocument ProcessRemaining(SourceGeneratorRazorCodeDocument sgDocument) - { - var codeDocument = sgDocument.CodeDocument; - Debug.Assert(codeDocument.GetReferencedTagHelpers() is not null); - - ProcessPartial(sgDocument.CodeDocument, rewritePhaseIndex, Engine.Phases.Count); - return new SourceGeneratorRazorCodeDocument(codeDocument); - } - - private void ProcessPartial(RazorCodeDocument codeDocument, int startIndex, int endIndex) - { - Debug.Assert(startIndex >= 0 && startIndex <= endIndex && endIndex <= Engine.Phases.Count); - for (var i = startIndex; i < endIndex; i++) - { - Engine.Phases[i].Execute(codeDocument); - } - } -} + private readonly int discoveryPhaseIndex = -1; + + private readonly int rewritePhaseIndex = -1; + + public SourceGeneratorProjectEngine(DefaultRazorProjectEngine projectEngine) + : base(projectEngine.Configuration, projectEngine.Engine, projectEngine.FileSystem, projectEngine.ProjectFeatures) + { + for (int i = 0; i < Engine.Phases.Count; i++) + { + if (Engine.Phases[i] is DefaultRazorTagHelperContextDiscoveryPhase) + { + discoveryPhaseIndex = i; + } + else if (Engine.Phases[i] is DefaultRazorTagHelperRewritePhase) + { + rewritePhaseIndex = i; + } + else if (discoveryPhaseIndex >= 0 && rewritePhaseIndex >= 0) + { + break; + } + } + Debug.Assert(discoveryPhaseIndex >= 0); + Debug.Assert(rewritePhaseIndex >= 0); + } + + public SourceGeneratorRazorCodeDocument ProcessInitialParse(RazorProjectItem projectItem, bool designTime) + { + var codeDocument = designTime + ? CreateCodeDocumentDesignTimeCore(projectItem) + : CreateCodeDocumentCore(projectItem); + ProcessPartial(codeDocument, 0, discoveryPhaseIndex); + + // record the syntax tree, before the tag helper re-writing occurs + codeDocument.SetPreTagHelperSyntaxTree(codeDocument.GetSyntaxTree()); + return new SourceGeneratorRazorCodeDocument(codeDocument); + } + + public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCodeDocument sgDocument, IReadOnlyList tagHelpers, bool checkForIdempotency) + { + Debug.Assert(sgDocument.CodeDocument.GetPreTagHelperSyntaxTree() is not null); + + int startIndex = discoveryPhaseIndex; + var codeDocument = sgDocument.CodeDocument; + var previousTagHelpers = codeDocument.GetTagHelpers(); + if (checkForIdempotency && previousTagHelpers is not null) + { + // compare the tag helpers with the ones the document last used + if (Enumerable.SequenceEqual(tagHelpers, previousTagHelpers)) + { + // tag helpers are the same, nothing to do! + return sgDocument; + } + else + { + // tag helpers have changed, figure out if we need to re-write + var oldContextHelpers = codeDocument.GetTagHelperContext().TagHelpers; + + // re-run the scope check to figure out which tag helpers this document can see + codeDocument.SetTagHelpers(tagHelpers); + Engine.Phases[discoveryPhaseIndex].Execute(codeDocument); + + // Check if any new tag helpers were added or ones we previously used were removed + var newContextHelpers = codeDocument.GetTagHelperContext().TagHelpers; + var added = newContextHelpers.Except(oldContextHelpers); + var referencedByRemoved = codeDocument.GetReferencedTagHelpers().Except(newContextHelpers); + if (!added.Any() && !referencedByRemoved.Any()) + { + // Either nothing new, or any that got removed weren't used by this document anyway + return sgDocument; + } + + // We need to re-write the document, but can skip the scoping as we just performed it + startIndex = rewritePhaseIndex; + } + } + else + { + codeDocument.SetTagHelpers(tagHelpers); + } + + ProcessPartial(codeDocument, startIndex, rewritePhaseIndex + 1); + return new SourceGeneratorRazorCodeDocument(codeDocument); + } + + public SourceGeneratorRazorCodeDocument ProcessRemaining(SourceGeneratorRazorCodeDocument sgDocument) + { + var codeDocument = sgDocument.CodeDocument; + Debug.Assert(codeDocument.GetReferencedTagHelpers() is not null); + + ProcessPartial(sgDocument.CodeDocument, rewritePhaseIndex, Engine.Phases.Count); + return new SourceGeneratorRazorCodeDocument(codeDocument); + } + + private void ProcessPartial(RazorCodeDocument codeDocument, int startIndex, int endIndex) + { + Debug.Assert(startIndex >= 0 && startIndex <= endIndex && endIndex <= Engine.Phases.Count); + for (var i = startIndex; i < endIndex; i++) + { + Engine.Phases[i].Execute(codeDocument); + } + } +} \ No newline at end of file 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 4f01653f5c2..ffeb06446cf 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 @@ -242,10 +242,10 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Index.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Pages/Counter.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Index.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Index.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Counter.razor"), + 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") ); @@ -442,8 +442,8 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. e => e.AssertSingleItem("RewriteTagHelpersStop", "Pages/Counter.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Pages/Counter.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "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") ); } @@ -812,8 +812,8 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. e => e.AssertSingleItem("RewriteTagHelpersStop", "Pages/Counter.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Pages/Counter.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "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") ); } @@ -976,10 +976,10 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Index.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Pages/Counter.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Index.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Index.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Counter.razor"), + 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") ); } @@ -1124,10 +1124,10 @@ protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components. e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Index.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Pages/Counter.razor"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Index.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Index.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Counter.razor"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Counter.razor"), + 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") ); @@ -1297,10 +1297,10 @@ internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Ra e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Index.cshtml"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Views/Shared/_Layout.cshtml"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Views/Shared/_Layout.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Index.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Index.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Views/Shared/_Layout.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Views/Shared/_Layout.cshtml"), + 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") ); @@ -1501,8 +1501,8 @@ internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Ra e => e.AssertSingleItem("RewriteTagHelpersStop", "Views/Shared/_Layout.cshtml"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Views/Shared/_Layout.cshtml"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Views/Shared/_Layout.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Views/Shared/_Layout.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Views/Shared/_Layout.cshtml"), + 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") ); } @@ -1911,8 +1911,8 @@ internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.Razor e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Pages/Index.cshtml"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStart", "Views/Shared/_Layout.cshtml"), e => e.AssertSingleItem("CheckAndRewriteTagHelpersStop", "Views/Shared/_Layout.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStart", "Pages/Index.cshtml"), - e => e.AssertSingleItem("RazorCodeGenerateStop", "Pages/Index.cshtml"), + e => e.AssertPair("RazorCodeGenerateStart", "Pages/Index.cshtml", "Runtime"), + e => e.AssertPair("RazorCodeGenerateStop", "Pages/Index.cshtml", "Runtime"), e => e.AssertSingleItem("AddSyntaxTrees", "Pages_Index_cshtml.g.cs") ); } 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 0639753e1e7..e0fd0109241 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 @@ -29,6 +29,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; @@ -55,10 +56,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"; @@ -354,7 +356,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 { @@ -369,6 +371,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; + } + public static GeneratorRunResult VerifyOutputsMatchBaseline(this GeneratorRunResult result, [CallerFilePath] string testPath = null!, [CallerMemberName] string testName = null!) { @@ -427,9 +452,9 @@ private static void GenerateOutputBaseline(string baselinePath, string text) File.WriteAllText(baselinePath, text, _baselineEncoding); } - private static string GenerateExpectedOutput(GeneratorRunResult result) + private static string GenerateExpectedPageOutput(GeneratorRunResult result) { - StringBuilder sb = new StringBuilder("Generated Output:").AppendLine().AppendLine(); + StringBuilder sb = new StringBuilder("Generated Page Output:").AppendLine().AppendLine(); for (int i = 0; i < result.GeneratedSources.Length; i++) { if (i > 0) @@ -441,6 +466,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); @@ -476,4 +517,12 @@ public static void AssertSingleItem(this RazorEventListener.RazorEvent e, string 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]); + } } From 4993c7d67718d86658e4efad8c056f357b11cf08 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 25 Apr 2023 09:24:40 -0700 Subject: [PATCH 08/22] WIP --- Directory.Build.props | 3 ++- ...icrosoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj | 3 --- ...icrosoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj | 3 --- .../src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj | 3 --- .../src/Microsoft.CodeAnalysis.Razor.csproj | 1 + .../Microsoft.NET.Sdk.Razor.SourceGenerators.csproj | 3 --- .../Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj | 4 ++-- .../Microsoft.AspNetCore.Razor.LanguageServer.csproj | 4 ++-- .../ProjectSystem/DocumentState.cs | 2 +- .../ProjectSystem/ProjectSnapshot.cs | 2 +- .../Microsoft.VisualStudio.Editor.Razor.csproj | 4 ++-- 11 files changed, 11 insertions(+), 21 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 95c16199319..f9a2ef2f4a3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -58,12 +58,13 @@ + - + diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj index 0ba7e7de568..5442e0f2c7a 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj @@ -22,9 +22,6 @@ - - - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj index 562cf5c4010..5442e0f2c7a 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj @@ -22,9 +22,6 @@ - - - diff --git a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj index 67b5084793b..49f315cb0ef 100644 --- a/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj +++ b/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj @@ -19,9 +19,6 @@ - - - diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj b/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj index 198a293d099..bf604f740c8 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor/src/Microsoft.CodeAnalysis.Razor.csproj @@ -10,5 +10,6 @@ + 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 38cd41c4d64..54d554ab0a0 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 @@ -33,9 +33,6 @@ - - - diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj index cab90a78020..fa91851c98c 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj @@ -14,14 +14,14 @@ - + diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj index eab4d8de4b9..a2170f22567 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Microsoft.AspNetCore.Razor.LanguageServer.csproj @@ -42,10 +42,10 @@ - + diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index 0c2c2094310..1d96f2a3806 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -538,7 +538,7 @@ static void PropagateToTaskCompletionSource( //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Generated document locally"); } - return (codeDocument, inputVersion); + return (codeDocument!, inputVersion); } private static async Task GetRazorSourceDocumentAsync(IDocumentSnapshot document, RazorProjectItem? projectItem) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs index a28e74fb828..52ff8a03be7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs @@ -111,7 +111,7 @@ public virtual RazorProjectEngine GetProjectEngine() return State.ProjectEngine; } - private GeneratorDriverRunResult? _runResult; + //private GeneratorDriverRunResult? _runResult; public async Task<(string CSharp, string Html)> GetGeneratedDocumentsAsync(IDocumentSnapshot documentSnapshot) { diff --git a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj index 3f6d078db48..e5ba69ac8ac 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj +++ b/src/Razor/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj @@ -26,14 +26,14 @@ - + From a75e1269ab1ab32a4d648c1b8b8b5953c69e7cee Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 26 Apr 2023 14:02:08 -0700 Subject: [PATCH 09/22] Make local roslyn work again --- Directory.Build.props | 33 ++++++++++++++----- ...re.Razor.LanguageServer.Test.Common.csproj | 2 ++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f9a2ef2f4a3..88f936da660 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -53,22 +53,39 @@ --> + + + + + + + + + + + - - - - - - - - + + $(NoWarn);MSB3243 MIT Microsoft ASP.NET Core diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj index acd9ae32938..59afdf004f5 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj @@ -20,6 +20,8 @@ + + From f51418fc56238ac404b0891abfa7b93895dcaeb9 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 26 Apr 2023 14:02:32 -0700 Subject: [PATCH 10/22] Support source spaces in itemcollection --- .../Serialization/ItemCollectionConverter.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs index 3569a9b1aae..e8b0639c343 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs @@ -38,16 +38,30 @@ public override void WriteJson(JsonWriter writer, ItemCollection? value, JsonSer foreach (var pair in value) { - if (pair.Key is string k && pair.Value is string v) + if (pair.Key is string k) { writer.WritePropertyName(k); - writer.WriteValue(v); } else { Debug.Assert(false, $"Cannot serialize non-string annotation {pair}"); } - } + + switch (pair.Value) + { + case string s: + writer.WriteValue(s); + break; + + case SourceSpan span: + SourceSpanConverter.Instance.WriteJson(writer, span, serializer); + break; + + default: + Debug.Assert(false, $"Cannot serialize non-string annotation {pair}"); + break; + } + } writer.WriteEndObject(); } From 7c103fc4f2a27d1232df9eb1f87247290eb39871 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 26 Apr 2023 14:04:31 -0700 Subject: [PATCH 11/22] Dont use binder --- .../Serialization/RazorCodeDocumentSerializer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs index 4c86fb599c9..351d29726fd 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs @@ -46,7 +46,6 @@ private RazorCodeDocumentSerializer() }, ContractResolver = new RazorContractResolver(), TypeNameHandling = TypeNameHandling.Auto, - SerializationBinder = new RazorSerializationBinder(), DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, }; } From 2ebfabf834dd8fc6522a119bb554c03fa43975ff Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 26 Apr 2023 14:05:17 -0700 Subject: [PATCH 12/22] Support intermediate extension nodes --- .../Serialization/RazorContractResolver.cs | 5 +++++ .../Serialization/SourceSpanConverter.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorContractResolver.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorContractResolver.cs index e28847598e8..92c76a05eff 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorContractResolver.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorContractResolver.cs @@ -32,6 +32,11 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ property.Ignored = true; } + if (typeof(ExtensionIntermediateNode).IsAssignableFrom(property.DeclaringType)) + { + property.ItemTypeNameHandling = TypeNameHandling.All; + } + return property; } diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/SourceSpanConverter.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/SourceSpanConverter.cs index 3f7d71d12b8..1d291185e60 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/SourceSpanConverter.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/SourceSpanConverter.cs @@ -7,6 +7,8 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators; internal sealed class SourceSpanConverter : JsonConverter { + public static SourceSpanConverter Instance = new(); + public override SourceSpan? ReadJson(JsonReader reader, Type objectType, SourceSpan? existingValue, bool hasExistingValue, JsonSerializer serializer) { var span = JObject.Load(reader); From e0a242137f91258492f33362d0b13369a7970715 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 26 Apr 2023 14:47:50 -0700 Subject: [PATCH 13/22] WIP --- ...soft.NET.Sdk.Razor.SourceGenerators.csproj | 1 + .../RazorSourceGenerator.cs | 1 + .../RazorCodeDocumentSerializer.cs | 4 +- .../BoundAttributeDescriptorJsonConverter.cs | 180 ++++++++++++++++++ .../DefaultProjectSnapshotManagerAccessor.cs | 12 +- .../TagHelperDescriptorJsonConverter.cs | 1 + ...osoft.CodeAnalysis.Razor.Workspaces.csproj | 1 + .../ProjectSystem/DocumentState.cs | 20 +- .../GeneratorSnapshotProvider.cs | 2 +- .../ProjectSystem/ProjectSnapshot.cs | 4 +- .../RazorInProcGeneratorSnapshotDeely.cs | 14 +- 11 files changed, 217 insertions(+), 23 deletions(-) create mode 100644 src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs 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 54d554ab0a0..ca9a0759543 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 @@ -20,6 +20,7 @@ + diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs index d4ea167c61d..dbe8e212f0d 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs @@ -318,6 +318,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { var (filePath, document) = pair; var hintName = GetIdentifierFromPath(filePath); + context.AddOutput(hintName + ".rsg.json", RazorCodeDocumentSerializer.Instance.Serialize(document.CodeDocument)); context.AddOutput(hintName + ".rsg.cs", document.CodeDocument.GetCSharpDocument().GeneratedCode); context.AddOutput(hintName + ".rsg.html", document.CodeDocument.GetHtmlDocument().GeneratedCode); }); diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs index 351d29726fd..5b775e27619 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs @@ -11,7 +11,7 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators; -internal sealed class RazorCodeDocumentSerializer +public sealed class RazorCodeDocumentSerializer { private const string TagHelperContext = nameof(TagHelperContext); private const string ParserOptions = nameof(ParserOptions); @@ -47,7 +47,7 @@ private RazorCodeDocumentSerializer() ContractResolver = new RazorContractResolver(), TypeNameHandling = TypeNameHandling.Auto, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, - }; + }; } public RazorCodeDocument? Deserialize(string json, RazorSourceDocument source) diff --git a/src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs b/src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs new file mode 100644 index 00000000000..3fea533c8da --- /dev/null +++ b/src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs @@ -0,0 +1,180 @@ +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. + +//#nullable disable + +//using System; +//using System.Collections.Generic; +//using Microsoft.AspNetCore.Razor.Language; +//using Newtonsoft.Json; + +//namespace Microsoft.CodeAnalysis.Razor.Serialization; + +//internal class BoundAttributeDescriptorJsonConverter : JsonConverter +//{ +// public override bool CanConvert(Type objectType) +// { +// return typeof(BoundAttributeDescriptor).IsAssignableFrom(objectType); +// } + +// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) +// { +// BoundAttributeDescriptorBuilder builder = new DefaultBoundAttributeDescriptorBuilder(); +// R +// } + +// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) +// { +// } + +// public static void ReadBoundAttribute(JsonReader reader, BoundAttributeDescriptorBuilder attribute) +// { +// //reader.ReadProperties(propertyName => +// { +// switch (propertyName) +// { +// case nameof(BoundAttributeDescriptor.Name): +// if (reader.Read()) +// { +// var name = (string)reader.Value; +// attribute.Name = name; +// } +// break; +// case nameof(BoundAttributeDescriptor.TypeName): +// if (reader.Read()) +// { +// var typeName = (string)reader.Value; +// attribute.TypeName = typeName; +// } +// break; +// case nameof(BoundAttributeDescriptor.Documentation): +// if (reader.Read()) +// { +// var documentation = (string)reader.Value; +// attribute.Documentation = documentation; +// } +// break; +// case nameof(BoundAttributeDescriptor.IndexerNamePrefix): +// if (reader.Read()) +// { +// var indexerNamePrefix = (string)reader.Value; +// if (indexerNamePrefix != null) +// { +// attribute.IsDictionary = true; +// attribute.IndexerAttributeNamePrefix = indexerNamePrefix; +// } +// } +// break; +// case nameof(BoundAttributeDescriptor.IndexerTypeName): +// if (reader.Read()) +// { +// var indexerTypeName = (string)reader.Value; +// if (indexerTypeName != null) +// { +// attribute.IsDictionary = true; +// attribute.IndexerValueTypeName = indexerTypeName; +// } +// } +// break; +// case nameof(BoundAttributeDescriptor.IsEnum): +// if (reader.Read()) +// { +// var isEnum = (bool)reader.Value; +// attribute.IsEnum = isEnum; +// } +// break; +// case nameof(BoundAttributeDescriptor.IsEditorRequired): +// if (reader.Read()) +// { +// var value = (bool)reader.Value; +// attribute.IsEditorRequired = value; +// } +// break; +// case nameof(BoundAttributeDescriptor.BoundAttributeParameters): +// ReadBoundAttributeParameters(reader, attribute); +// break; +// case nameof(BoundAttributeDescriptor.Diagnostics): +// TagHelperDescriptorJsonConverter.ReadDiagnostics(reader, attribute.Diagnostics); +// break; +// case nameof(BoundAttributeDescriptor.Metadata): +// TagHelperDescriptorJsonConverter.ReadMetadata(reader, attribute.Metadata); +// break; +// } +// }); +// } + +// private static void ReadBoundAttributeParameters(JsonReader reader, BoundAttributeDescriptorBuilder builder) +// { +// if (!reader.Read()) +// { +// return; +// } + +// if (reader.TokenType != JsonToken.StartArray) +// { +// return; +// } + +// do +// { +// ReadBoundAttributeParameter(reader, builder); +// } while (reader.TokenType != JsonToken.EndArray); +// } + +// private static void ReadBoundAttributeParameter(JsonReader reader, BoundAttributeDescriptorBuilder builder) +// { +// if (!reader.Read()) +// { +// return; +// } + +// if (reader.TokenType != JsonToken.StartObject) +// { +// return; +// } + +// builder.BindAttributeParameter(parameter => +// { +// reader.ReadProperties(propertyName => +// { +// switch (propertyName) +// { +// case nameof(BoundAttributeParameterDescriptor.Name): +// if (reader.Read()) +// { +// var name = (string)reader.Value; +// parameter.Name = name; +// } +// break; +// case nameof(BoundAttributeParameterDescriptor.TypeName): +// if (reader.Read()) +// { +// var typeName = (string)reader.Value; +// parameter.TypeName = typeName; +// } +// break; +// case nameof(BoundAttributeParameterDescriptor.IsEnum): +// if (reader.Read()) +// { +// var isEnum = (bool)reader.Value; +// parameter.IsEnum = isEnum; +// } +// break; +// case nameof(BoundAttributeParameterDescriptor.Documentation): +// if (reader.Read()) +// { +// var documentation = (string)reader.Value; +// parameter.Documentation = documentation; +// } +// break; +// case nameof(BoundAttributeParameterDescriptor.Metadata): +// TagHelperDescriptorJsonConverter.ReadMetadata(reader, parameter.Metadata); +// break; +// case nameof(BoundAttributeParameterDescriptor.Diagnostics): +// TagHelperDescriptorJsonConverter.ReadDiagnostics(reader, parameter.Diagnostics); +// break; +// } +// }); +// }); +// } +//} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs index 0543fd87d16..c3fc0f4a36a 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs @@ -29,15 +29,17 @@ public LspHostOutput(ClientNotifierServiceBase notifier) _notifier = notifier; } - public async Task<(string CSharp, string Html)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) + public async Task<(string CSharp, string Html, string Json)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) { var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("/")); - var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? "") + ".g.cs"; + var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? ""); - var csharp = await RequestOutput(documentName + ".rsg-cs"); - var html = await RequestOutput(documentName + ".rsg-html"); + var csharp = await RequestOutput(documentName + ".rsg.cs"); + var html = await RequestOutput(documentName + ".rsg.html"); + var json = await RequestOutput(documentName + ".rsg.json"); - return (csharp, html); + + return (csharp, html, json); async Task RequestOutput(string name) { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/TagHelperDescriptorJsonConverter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/TagHelperDescriptorJsonConverter.cs index 6d574e5a694..0bf12d9f46e 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/TagHelperDescriptorJsonConverter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/TagHelperDescriptorJsonConverter.cs @@ -218,6 +218,7 @@ private static void WriteAllowedChildTags(JsonWriter writer, AllowedChildTagDesc writer.WriteEndObject(); } + // TODO: looks like we have bound attributes not part of tag helpers. We need to extract this to be an actual type converter and call into it here instead. private static void WriteBoundAttribute(JsonWriter writer, BoundAttributeDescriptor boundAttribute, JsonSerializer serializer) { writer.WriteStartObject(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj index c6331c63437..45a747d62f9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj @@ -29,6 +29,7 @@ + diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index 1d96f2a3806..d6265dffac4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.NET.Sdk.Razor.SourceGenerators; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -518,17 +519,20 @@ static void PropagateToTaskCompletionSource( var codeDoc2 = await project.GetGeneratedDocumentsAsync(document); RazorCodeDocument? codeDocument = null; - if (codeDoc2.CSharp != "") + if (codeDoc2.Json != "") { - codeDocument = RazorCodeDocument.Create(documentSource, importSources); - codeDocument.SetTagHelpers(project.TagHelpers); // TODO: are these only used for processing, or are they used elsewhere? - codeDocument.SetFileKind(document.FileKind); + codeDocument = RazorCodeDocumentSerializer.Instance.Deserialize(codeDoc2.Json, documentSource); // TODO: do we need importSources? + var codeDoc3 = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); - var csharpDocument = RazorCSharpDocument.Create(codeDocument, codeDoc2.CSharp, RazorCodeGenerationOptions.CreateDesignTimeDefault(), Array.Empty()); - codeDocument.SetCSharpDocument(csharpDocument); + //codeDocument = RazorCodeDocument.Create(documentSource, importSources); + //codeDocument.SetTagHelpers(project.TagHelpers); // TODO: are these only used for processing, or are they used elsewhere? + //codeDocument.SetFileKind(document.FileKind); - var htmlDocument = RazorHtmlDocument.Create(codeDocument, codeDoc2.Html, RazorCodeGenerationOptions.CreateDesignTimeDefault(), Array.Empty()); - codeDocument.SetHtmlDocument(htmlDocument); + //var csharpDocument = RazorCSharpDocument.Create(codeDocument, codeDoc2.CSharp, RazorCodeGenerationOptions.CreateDesignTimeDefault(), Array.Empty()); + //codeDocument.SetCSharpDocument(csharpDocument); + + //var htmlDocument = RazorHtmlDocument.Create(codeDocument, codeDoc2.Html, RazorCodeGenerationOptions.CreateDesignTimeDefault(), Array.Empty()); + //codeDocument.SetHtmlDocument(htmlDocument); //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Retrieved document from service"); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs index 66eb9bc63da..8e0d0f65b0d 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Razor; internal interface IGeneratorSnapshotProvider : IWorkspaceService { - Task<(string CSharp, string Html)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot); + Task<(string CSharp, string Html, string Json)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot); } //internal class GeneratorSnapshotProvider : IWorkspaceService diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs index 52ff8a03be7..cce3b388c90 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs @@ -113,7 +113,7 @@ public virtual RazorProjectEngine GetProjectEngine() //private GeneratorDriverRunResult? _runResult; - public async Task<(string CSharp, string Html)> GetGeneratedDocumentsAsync(IDocumentSnapshot documentSnapshot) + public async Task<(string CSharp, string Html, string Json)> GetGeneratedDocumentsAsync(IDocumentSnapshot documentSnapshot) { //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("GetCodeDocumentAsync: "+filePath); @@ -156,6 +156,6 @@ public virtual RazorProjectEngine GetProjectEngine() // TODO: extract from the run-result the actual file // PROTOTYPE: how do we handle the case where we couldn't get the result? - return ("", ""); + return ("", "", ""); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs index cb9ec332ea8..c6fba93fddf 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs @@ -34,23 +34,27 @@ public RazorInProcGeneratorSnapshotDeely(Workspace workspace) _workspace = workspace; } - public async Task<(string CSharp, string Html)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) + public async Task<(string CSharp, string Html, string Json)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) { string? csharp = null; string? html = null; + string? json = null; + var project = _workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == documentSnapshot.Project.FilePath); if (project is not null) { // PROTOTYPE: factor this out so we can share it var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("\\")); - var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? "") + ".g.cs"; + var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? ""); + + csharp = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg.cs", System.Threading.CancellationToken.None); + html = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg.html", System.Threading.CancellationToken.None); + json = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg.json", System.Threading.CancellationToken.None); - csharp = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg-cs", System.Threading.CancellationToken.None); - html = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg-html", System.Threading.CancellationToken.None); } - return (csharp ?? "", html ?? ""); + return (csharp ?? "", html ?? "", json ?? ""); } // PROTOTYPE: copied from the generator From 05967ffbb9133203983f134085282d3841e11a4c Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 8 May 2023 16:32:54 -0700 Subject: [PATCH 14/22] Track tag helper replacement as we go --- .../src/DefaultRazorTagHelperRewritePhase.cs | 16 ++++++++++++--- .../src/Legacy/TagHelperParseTreeRewriter.cs | 12 ++++++++++- .../src/RazorCodeDocumentExtensions.cs | 20 +++++++++++++++++++ .../test/Legacy/TagHelperRewritingTestBase.cs | 2 +- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperRewritePhase.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperRewritePhase.cs index cb8632d70b0..3a465eaacc3 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperRewritePhase.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperRewritePhase.cs @@ -9,20 +9,30 @@ namespace Microsoft.AspNetCore.Razor.Language; internal sealed class DefaultRazorTagHelperRewritePhase : RazorEnginePhaseBase { protected override void ExecuteCore(RazorCodeDocument codeDocument) + { + var context = codeDocument.GetTagHelperContext(); + if (context is not null) + { + RewriteTagHelpers(codeDocument, context.Prefix, context.TagHelpers); + } + } + + public void RewriteTagHelpers(RazorCodeDocument codeDocument, string prefix, IReadOnlyList tagHelpers) { var syntaxTree = codeDocument.GetPreTagHelperSyntaxTree() ?? codeDocument.GetSyntaxTree(); ThrowForMissingDocumentDependency(syntaxTree); - var context = codeDocument.GetTagHelperContext(); - if (context?.TagHelpers.Count > 0) + if (tagHelpers.Count > 0) { - var rewrittenSyntaxTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, context.Prefix, context.TagHelpers, out var usedHelpers); + var rewrittenSyntaxTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, prefix, tagHelpers, out var usedHelpers, out var rewrittenMap); codeDocument.SetSyntaxTree(rewrittenSyntaxTree); codeDocument.SetReferencedTagHelpers(usedHelpers); + codeDocument.SetRewrittenTagHelpers(rewrittenMap); } else { codeDocument.SetReferencedTagHelpers(new HashSet()); + codeDocument.SetRewrittenTagHelpers(new Dictionary()); } } } diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs index d0ed970175f..f6105adf5f2 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/Legacy/TagHelperParseTreeRewriter.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy; internal static class TagHelperParseTreeRewriter { - public static RazorSyntaxTree Rewrite(RazorSyntaxTree syntaxTree, string tagHelperPrefix, IEnumerable descriptors, out ISet usedDescriptors) + public static RazorSyntaxTree Rewrite(RazorSyntaxTree syntaxTree, string tagHelperPrefix, IEnumerable descriptors, out ISet usedDescriptors, out Dictionary rewrittenMap) { var errorSink = new ErrorSink(); @@ -34,6 +34,7 @@ public static RazorSyntaxTree Rewrite(RazorSyntaxTree syntaxTree, string tagHelp var diagnostics = CombineErrors(syntaxTree.Diagnostics, errorList).OrderBy(error => error.Span.AbsoluteIndex); usedDescriptors = rewriter.UsedDescriptors; + rewrittenMap = rewriter.Rewritten; var newSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, diagnostics, syntaxTree.Options); return newSyntaxTree; @@ -65,6 +66,10 @@ internal sealed class Rewriter : SyntaxRewriter private readonly RazorParserFeatureFlags _featureFlags; private readonly HashSet _usedDescriptors; + // PROTOTYPE: + private readonly Dictionary _rewritten; + + public Rewriter( RazorSourceDocument source, string tagHelperPrefix, @@ -80,9 +85,12 @@ public Rewriter( _htmlAttributeTracker = new List>(); _featureFlags = featureFlags; _usedDescriptors = new HashSet(); + _rewritten = new Dictionary(); _errorSink = errorSink; } public HashSet UsedDescriptors => _usedDescriptors; + public Dictionary Rewritten => _rewritten; + private TagTracker CurrentTracker => _trackerStack.Count > 0 ? _trackerStack.Peek() : null; @@ -286,6 +294,8 @@ private bool TryRewriteTagHelperStart( // We're in a start TagHelper block. ValidateStartTagSyntax(tagName, startTag); + _rewritten.Add(startTag, tagHelperBinding); + var rewrittenStartTag = TagHelperBlockRewriter.Rewrite( tagName, _featureFlags, diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs index 17b1ace47ea..063cb3c29ff 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs @@ -81,6 +81,26 @@ internal static void SetReferencedTagHelpers(this RazorCodeDocument document, IS document.Items[nameof(GetReferencedTagHelpers)] = tagHelpers; } + internal static Dictionary GetRewrittenTagHelpers(this RazorCodeDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return document.Items[nameof(GetRewrittenTagHelpers)] as Dictionary; + } + + internal static void SetRewrittenTagHelpers(this RazorCodeDocument document, Dictionary tagHelpers) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + document.Items[nameof(SetRewrittenTagHelpers)] = tagHelpers; + } + public static RazorSyntaxTree GetPreTagHelperSyntaxTree(this RazorCodeDocument document) { if (document == null) diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Legacy/TagHelperRewritingTestBase.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Legacy/TagHelperRewritingTestBase.cs index dc4397aa417..89b039d4c53 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Legacy/TagHelperRewritingTestBase.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Legacy/TagHelperRewritingTestBase.cs @@ -40,7 +40,7 @@ internal void EvaluateData( { var syntaxTree = ParseDocument(documentContent, featureFlags: featureFlags); - var rewrittenTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors, out _); + var rewrittenTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors, out _, out _); Assert.Equal(syntaxTree.Root.FullWidth, rewrittenTree.Root.FullWidth); From 08fe81b27d9806c90220a57eb10a09058de7a1b3 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 8 May 2023 16:33:54 -0700 Subject: [PATCH 15/22] Match tooling generated code --- .../RazorSourceGenerator.RazorProviders.cs | 2 +- .../RazorSourceGenerator.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) 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 6bcd532c45c..a160811ef20 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.RazorProviders.cs @@ -53,7 +53,7 @@ private static (RazorSourceGenerationOptions?, Diagnostic?) ComputeRazorSourceGe razorLanguageVersionString); } - var razorConfiguration = RazorConfiguration.Create(razorLanguageVersion, configurationName ?? "default", System.Linq.Enumerable.Empty(), true); + var razorConfiguration = RazorConfiguration.Create(razorLanguageVersion, configurationName ?? "default", System.Linq.Enumerable.Empty(), false); var razorSourceGenerationOptions = new RazorSourceGenerationOptions() { diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs index dbe8e212f0d..a46a0dc503b 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/RazorSourceGenerator.cs @@ -113,7 +113,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (root is CompilationUnitSyntax { Members: [NamespaceDeclarationSyntax { Members: [ClassDeclarationSyntax classSyntax, ..] }, ..] }) { var declaredClass = compilationWithDeclarations.GetSemanticModel(generatedDeclarationSyntaxTree).GetDeclaredSymbol(classSyntax, ct); - Debug.Assert(declaredClass is null || declaredClass is { AllInterfaces: [{ Name: "IComponent" }, ..] }); + + // PROTOTYPE: fix this + //Debug.Assert(declaredClass is null || declaredClass is { AllInterfaces: [{ Name: "IComponent" }, ..] }); targetSymbol = declaredClass ?? targetSymbol; } From 9e0748dc7113303ada372b862d1346f181b75662 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 8 May 2023 20:42:01 -0700 Subject: [PATCH 16/22] Update versions --- eng/Versions.props | 78 +++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 3cab4806ab4..93a254092d0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -36,8 +36,8 @@ imported. This OK because we want to just have an obvious salt for a local build. --> - 17.6.0 - 17.6 + 17.7.2 + 17.7 $(AddinMajorVersion) $(AddinVersion).$(OfficialBuildId) $(AddinVersion).42424242.42 @@ -55,6 +55,28 @@ 6.0.2-servicing.22064.6 6.0.1 + 8.0.0-alpha.1.23214.4 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 4.7.0-2.23253.2 + 8.0.0-beta.23218.3 + 1.0.0-beta.23218.1 1.1.2-beta1.22512.1 - 17.5.0-preview-2-33117-317 - 17.6.76-preview - 4.6.0-3.23164.6 - 17.6.15-preview - 4.6.0-2.23128.3 - $(RoslynPackageVersion) + 17.6.35829 + 17.6.252 + 17.6.22 + + 6.0.0 6.0.0 - 7.0.0 + 6.0.0 7.0.0 6.0.0 6.0.0 @@ -88,8 +109,10 @@ 0.13.2.2052 1.2.6 16.8.0 + 2.0.0 6.0.0-alpha.1.21072.5 + $(MicrosoftNetCompilersToolsetPackageVersion) $(Tooling_MicrosoftCodeAnalysisTestingVersion) $(Tooling_MicrosoftCodeAnalysisTestingVersion) $(MicrosoftVisualStudioPackagesVersion) @@ -97,7 +120,7 @@ $(MicrosoftVisualStudioExtensibilityTestingXunitVersion) $(MicrosoftVisualStudioPackagesVersion) $(MicrosoftVisualStudioPackagesVersion) - 17.3.37-preview + 17.6.26-preview $(VisualStudioLanguageServerProtocolVersion) $(VisualStudioLanguageServerProtocolVersion) $(VisualStudioLanguageServerProtocolVersion) @@ -106,18 +129,18 @@ $(MicrosoftVisualStudioShellPackagesVersion) $(MicrosoftVisualStudioShellPackagesVersion) $(MicrosoftVisualStudioShellPackagesVersion) - 17.5.17-alpha - 16.6.15 + 17.6.10-preview + 17.6.44 $(MicrosoftVisualStudioPackagesVersion) $(MicrosoftVisualStudioPackagesVersion) $(MicrosoftVisualStudioPackagesVersion) - 17.5.10-alpha + 17.6.40 16.10.0-preview-1-31008-014 - 17.0.71 + 17.6.11 $(Tooling_HtmlEditorPackageVersion) $(Tooling_HtmlEditorPackageVersion) $(Tooling_HtmlEditorPackageVersion) @@ -126,33 +149,18 @@ 1.3.8 1.0.15 4.16.0 - 13.0.1 + 13.0.3 2.9.112 4.8.0 0.19.5 $(OmniSharpExtensionsLanguageServerPackageVersion) 1.39.1 - 2.14.20 + 2.15.26 4.3.0 3.3.4 - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) - $(RoslynPackageVersion) $(Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion) $(Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion) - $(RoslynPackageVersion) + $(MicrosoftVisualStudioLanguageServicesPackageVersion) 0.10.0 1.4.1 2.4.2 @@ -170,11 +178,11 @@ $(MicrosoftBuildVersion) $(MicrosoftBuildVersion) 4.7.0 - 1.0.0-20200708.1 + 1.0.0-20230414.1 6.0.0 6.0.0-preview.7.21377.19 4.10.0 - 13.0.1 + 13.0.3 6.0.0 5.0.0 6.0.0 @@ -184,4 +192,4 @@ 7.0.0-preview.5.22528.1 - + \ No newline at end of file From f0618de5313d8a624c98fd3de2bd4f7d6695255d Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 9 May 2023 14:44:58 -0700 Subject: [PATCH 17/22] Cleanup document state --- .../ProjectSystem/DocumentState.cs | 132 ++++++++---------- 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index d6265dffac4..1a7fbdbcc49 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -353,52 +353,52 @@ public bool IsResultAvailable TaskCompletionSource<(RazorCodeDocument, VersionStamp)>? tcs = null; lock (_lock) + { + if (_taskUnsafeReference is null || + !_taskUnsafeReference.TryGetTarget(out taskUnsafe)) { - if (_taskUnsafeReference is null || - !_taskUnsafeReference.TryGetTarget(out taskUnsafe)) - { - // So this is a bit confusing. Instead of directly calling the Razor parser inside of this lock we create an indirect TaskCompletionSource - // to represent when it completes. The reason behind this is that there are several scenarios in which the Razor parser will run synchronously - // (mostly all in VS) resulting in this lock being held for significantly longer than expected. To avoid threads queuing up repeatedly on the - // above lock and blocking we can allow those threads to await asynchronously for the completion of the original parse. - - tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); - taskUnsafe = tcs.Task; - _taskUnsafeReference = new WeakReference>(taskUnsafe); - } + // So this is a bit confusing. Instead of directly calling the Razor parser inside of this lock we create an indirect TaskCompletionSource + // to represent when it completes. The reason behind this is that there are several scenarios in which the Razor parser will run synchronously + // (mostly all in VS) resulting in this lock being held for significantly longer than expected. To avoid threads queuing up repeatedly on the + // above lock and blocking we can allow those threads to await asynchronously for the completion of the original parse. + + tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + taskUnsafe = tcs.Task; + _taskUnsafeReference = new WeakReference>(taskUnsafe); } + } - if (tcs is null) - { - // There's no task completion source created meaning a value was retrieved from cache, just return it. - return taskUnsafe; - } + if (tcs is null) + { + // There's no task completion source created meaning a value was retrieved from cache, just return it. + return taskUnsafe; + } - // Typically in VS scenarios this will run synchronously because all resources are readily available. - var outputTask = ComputeGeneratedOutputAndVersionAsync(project, document); - if (outputTask.IsCompleted) - { - // Compiling ran synchronously, lets just immediately propagate to the TCS - PropagateToTaskCompletionSource(outputTask, tcs); - } - else - { - // Task didn't run synchronously (most likely outside of VS), lets allocate a bit more but utilize ContinueWith - // to properly connect the output task and TCS - _ = outputTask.ContinueWith( - static (task, state) => - { - Assumes.NotNull(state); - var tcs = (TaskCompletionSource<(RazorCodeDocument, VersionStamp)>)state; - - PropagateToTaskCompletionSource(task, tcs); - }, - tcs, - CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default); - } + // Typically in VS scenarios this will run synchronously because all resources are readily available. + var outputTask = ComputeGeneratedOutputAndVersionAsync(project, document); + if (outputTask.IsCompleted) + { + // Compiling ran synchronously, lets just immediately propagate to the TCS + PropagateToTaskCompletionSource(outputTask, tcs); } + else + { + // Task didn't run synchronously (most likely outside of VS), lets allocate a bit more but utilize ContinueWith + // to properly connect the output task and TCS + _ = outputTask.ContinueWith( + static (task, state) => + { + Assumes.NotNull(state); + var tcs = (TaskCompletionSource<(RazorCodeDocument, VersionStamp)>)state; + + PropagateToTaskCompletionSource(task, tcs); + }, + tcs, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + } return taskUnsafe; @@ -428,6 +428,9 @@ static void PropagateToTaskCompletionSource( private async Task<(RazorCodeDocument, VersionStamp)> ComputeGeneratedOutputAndVersionAsync(ProjectSnapshot project, IDocumentSnapshot document) { + // PROTOTYPE: need to revisit these assumptions and check they hold still. Ideally we'd have a way to ask the LSP + // if we needed to update rather than encoding assumptions here. + // We only need to produce the generated code if any of our inputs is newer than the // previously cached output. // @@ -501,45 +504,24 @@ static void PropagateToTaskCompletionSource( // PROTOTYPE: it's at this point that we want to reach into the 'generator snapshot' and grab the generated documents - // actually, is that true? Surely we'd create the snapshot with the data already generated? No, maybe not. Hmm. Argh, not sure. - // lets see what it looks like we if were to ask the project for the data here. - - - - // So we have the source already, no need to serialize that. Then we have the codeDocument which is a layer over the top of the source doc - // The code doc has the imports, the source doc, the tag helpers, the file kind, the CSharp doc, the html doc, the options, the diagnostics and the source mappings + var serializedCodeDoc = await project.GetGeneratedDocumentsAsync(document); - // Tag helpers are interesting, because we have a copy of them here already. So might not need to serialize those - // The chsarp and html docs are easy - // The options are simple value pairs, so should be easy - // Diagnostics are... complex? Ok the diagnostic descriptors are slightly hard, because they have a callback to get the resource, but we should be able to just 'flatten' them on the IDE side as the languages will be the same. - // Source mappings are *shrug*? - // Huh, plus there is everything in the items collection which is... a lot. (SyntaxTrees etc) - - var codeDoc2 = await project.GetGeneratedDocumentsAsync(document); - - RazorCodeDocument? codeDocument = null; - if (codeDoc2.Json != "") + var codeDocument = RazorCodeDocumentSerializer.Instance.Deserialize(serializedCodeDoc.Json, documentSource, project.TagHelpers); ; + if (codeDocument is null) { - codeDocument = RazorCodeDocumentSerializer.Instance.Deserialize(codeDoc2.Json, documentSource); // TODO: do we need importSources? - var codeDoc3 = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); - - //codeDocument = RazorCodeDocument.Create(documentSource, importSources); - //codeDocument.SetTagHelpers(project.TagHelpers); // TODO: are these only used for processing, or are they used elsewhere? - //codeDocument.SetFileKind(document.FileKind); - - //var csharpDocument = RazorCSharpDocument.Create(codeDocument, codeDoc2.CSharp, RazorCodeGenerationOptions.CreateDesignTimeDefault(), Array.Empty()); - //codeDocument.SetCSharpDocument(csharpDocument); - - //var htmlDocument = RazorHtmlDocument.Create(codeDocument, codeDoc2.Html, RazorCodeGenerationOptions.CreateDesignTimeDefault(), Array.Empty()); - //codeDocument.SetHtmlDocument(htmlDocument); - - //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Retrieved document from service"); + // PROTOTYPE: if we can't get the doc, use an empty one + codeDocument = RazorCodeDocument.Create(documentSource, Array.Empty()); + codeDocument.SetCSharpDocument(new DefaultRazorCSharpDocument(codeDocument, "", null!, null!, null!, null!)); } else { - //codeDocument = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); - //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Generated document locally"); + // PROTOTYPE: we need to re-parse the syntax tree for the tag helpers. + // we should just use the rewritten tag helpers so its fast. + // Ideally we should map what was replaced so we can do it even faster + var prefix = codeDocument.GetTagHelperContext().Prefix; + var phase = new DefaultRazorTagHelperRewritePhase(); + + phase.RewriteTagHelpers(codeDocument!, prefix, codeDocument!.GetTagHelperContext().TagHelpers); } return (codeDocument!, inputVersion); From 47c0752a2ca8482bf7b6014e6cabdad3db7ff1a6 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 9 May 2023 14:45:09 -0700 Subject: [PATCH 18/22] Implement missing serialization --- .../Serialization/ItemCollectionConverter.cs | 13 +++- .../MetadataCollectionConverter.cs | 66 ++++++++++++++++++ .../RazorCodeDocumentSerializer.cs | 67 +++++++++++++++++-- 3 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/MetadataCollectionConverter.cs diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs index e8b0639c343..d8ab0497a60 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/ItemCollectionConverter.cs @@ -18,8 +18,17 @@ internal sealed class ItemCollectionConverter : JsonConverter while (reader.Read() && reader.TokenType == JsonToken.PropertyName) { var propertyName = (string)reader.Value!; - var value = reader.ReadAsString(); - result.Add(propertyName, value); + + reader.Read(); + if (reader.TokenType == JsonToken.String) + { + result.Add(propertyName, (string)reader.Value!); + } + else if(reader.TokenType == JsonToken.StartObject) + { + var sourceSpan = SourceSpanConverter.Instance.ReadJson(reader, typeof(SourceSpan), null, false, serializer); + result.Add(propertyName, sourceSpan); + } } Debug.Assert(reader.TokenType == JsonToken.EndObject); diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/MetadataCollectionConverter.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/MetadataCollectionConverter.cs new file mode 100644 index 00000000000..f8881adc5ec --- /dev/null +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/MetadataCollectionConverter.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Razor.Language; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.NET.Sdk.Razor.SourceGenerators; + +internal sealed class MetadataCollectionConverter : JsonConverter +{ + public override MetadataCollection? ReadJson(JsonReader reader, Type objectType, MetadataCollection? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartObject) + { + return null; + } + + //PROTOTYPE: we should special case 0,1,2,3 + //// read the length + //reader.Read(); + //Debug.Assert(reader.TokenType == JsonToken.PropertyName && reader.Value is string s && s == "Count"); + + //reader.Read(); + //Debug.Assert(reader.TokenType == JsonToken.Integer); + //var count = (int)reader.Value!; + + //var result = existingValue ?? count switch + //{ + // 0 => MetadataCollection.Empty, + // 1 or 2 or 3 => MetadataCollection. + //} + + var result = new Dictionary(); + while (reader.Read() && reader.TokenType == JsonToken.PropertyName) + { + var propertyName = (string)reader.Value!; + var value = reader.ReadAsString(); + + result.Add(propertyName, value ?? string.Empty); + } + + Debug.Assert(reader.TokenType == JsonToken.EndObject); + return MetadataCollection.Create(result); + } + + public override void WriteJson(JsonWriter writer, MetadataCollection? value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + writer.WriteStartObject(); + //writer.WritePropertyName("count"); + //writer.WriteValue(value.Count); + + foreach (var pair in value) + { + writer.WritePropertyName(pair.Key); + writer.WriteValue(pair.Value); + + } + writer.WriteEndObject(); + } +} diff --git a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs index 5b775e27619..8ba4e34db45 100644 --- a/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs +++ b/src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/Serialization/RazorCodeDocumentSerializer.cs @@ -6,8 +6,11 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.CodeAnalysis.Razor.Serialization; using Newtonsoft.Json; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; namespace Microsoft.NET.Sdk.Razor.SourceGenerators; @@ -22,6 +25,7 @@ public sealed class RazorCodeDocumentSerializer private const string CSharpDocument = nameof(CSharpDocument); private const string HtmlDocument = nameof(HtmlDocument); private const string Namespace = nameof(Namespace); + private const string ReferencedTagHelpers = nameof(ReferencedTagHelpers); private readonly JsonSerializer _serializer; @@ -43,21 +47,22 @@ private RazorCodeDocumentSerializer() new DirectiveTokenDescriptorConverter(), new ItemCollectionConverter(), new RazorSourceDocumentConverter(), + new MetadataCollectionConverter() }, ContractResolver = new RazorContractResolver(), TypeNameHandling = TypeNameHandling.Auto, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, - }; + }; } - public RazorCodeDocument? Deserialize(string json, RazorSourceDocument source) + public RazorCodeDocument? Deserialize(string json, RazorSourceDocument source, IReadOnlyCollection? tagHelpers = null) { using var textReader = new StringReader(json); using var jsonReader = new JsonTextReader(textReader); - return Deserialize(jsonReader, source); + return Deserialize(jsonReader, source, tagHelpers); } - public RazorCodeDocument? Deserialize(JsonReader reader, RazorSourceDocument source) + public RazorCodeDocument? Deserialize(JsonReader reader, RazorSourceDocument source, IReadOnlyCollection? tagHelpers = null) { if (!reader.Read() || reader.TokenType != JsonToken.StartObject) { @@ -76,6 +81,35 @@ private RazorCodeDocumentSerializer() case nameof(CssScope): document.SetCssScope(reader.ReadAsString()); break; + case ReferencedTagHelpers: + if (tagHelpers is null) + { + throw new ArgumentNullException(nameof(tagHelpers)); + } + + reader.Read(); //array start + Debug.Assert(reader.TokenType == JsonToken.StartArray); + + HashSet referencedTagHelpers = new HashSet(); + + reader.Read(); + while (reader.TokenType == JsonToken.Integer) + { + var hash = (int)(long)reader.Value!; + + // PROTOTYPE: we should build a map before deserializing + // how do we handle dupes? + var helper = tagHelpers.FirstOrDefault(t => t.GetHashCode() == hash); + if (helper is not null) + { + referencedTagHelpers.Add(helper); + } + reader.Read(); + } + Debug.Assert(reader.TokenType == JsonToken.EndArray); + + document.SetReferencedTagHelpers(referencedTagHelpers); + break; case nameof(TagHelperContext): if (reader.Read() && reader.TokenType == JsonToken.StartObject) { @@ -192,6 +226,25 @@ public void Serialize(JsonWriter writer, RazorCodeDocument? document) writer.WriteEndObject(); } + if (document.GetReferencedTagHelpers() is { Count: > 0 } referencedTagHelpers) + { + // PROTOTYPE: no prefix support yet + //if (tagHelperContext.Prefix is { } prefix) + //{ + // writer.WritePropertyName(nameof(TagHelperDocumentContext.Prefix)); + // writer.WriteValue(prefix); + //} + + //writer.WritePropertyName(ReferencedTagHelpers); + //writer.WriteStartArray(); + //foreach (var tagHelper in referencedTagHelpers) + //{ + // writer.WriteValue(tagHelper.GetHashCode()); + //} + //writer.WriteEndArray(); + //_serializer.Serialize(writer, referencedTagHelpers); + } + if (document.GetSyntaxTree() is { } syntaxTree) { writer.WritePropertyName(SyntaxTree); @@ -210,6 +263,12 @@ public void Serialize(JsonWriter writer, RazorCodeDocument? document) SerializeHtmlDocument(writer, htmlDocument); } + //if (document.GetRewrittenTagHelpers() is { } rewrittenTagHelpers) + //{ + // writer.WritePropertyName("RewrittenTagHelpers"); + // SerializeRerittenTagHelpers() + //} + document.TryComputeNamespace(fallbackToRootNamespace: true, check: false, out var @namespace); writer.WritePropertyName(Namespace); writer.WriteValue(@namespace); From 7f99d44cbadd2298f9185ffb5b59d43394c39238 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 9 May 2023 14:45:25 -0700 Subject: [PATCH 19/22] Fix merge break --- .../HtmlCSharp/TestLanguageServiceBroker.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/TestLanguageServiceBroker.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/TestLanguageServiceBroker.cs index 9b452dc0156..156b47b7038 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/TestLanguageServiceBroker.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/TestLanguageServiceBroker.cs @@ -186,4 +186,9 @@ public IAsyncEnumerable RequestMultipleAsync(ITextBuff { throw new NotImplementedException(); } + + public Task NotifyAsync(ILanguageClient languageClient, string method, JToken parameters, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } From 9860ab28d17e64c9b7f9c89dc2c97d906880dfd9 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 9 May 2023 14:48:36 -0700 Subject: [PATCH 20/22] Renames --- .../DefaultProjectSnapshotManagerAccessor.cs | 10 +++++----- .../DefaultProjectWorkspaceStateGenerator.cs | 2 +- ...> InProcRazorGeneratedDocumentProvider.cs} | 19 ++++++++----------- .../GeneratorSnapshotProvider.cs | 17 ++--------------- .../ProjectSystem/ProjectSnapshot.cs | 4 ++-- 5 files changed, 18 insertions(+), 34 deletions(-) rename src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/{RazorInProcGeneratorSnapshotDeely.cs => InProcRazorGeneratedDocumentProvider.cs} (73%) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs index c3fc0f4a36a..d059390cef6 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs @@ -20,16 +20,16 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer; -internal class LspHostOutput : IGeneratorSnapshotProvider +internal class LspRazorGeneratedDocumentProvider : IRazorGeneratedDocumentProvider { - ClientNotifierServiceBase _notifier; + readonly ClientNotifierServiceBase _notifier; - public LspHostOutput(ClientNotifierServiceBase notifier) + public LspRazorGeneratedDocumentProvider(ClientNotifierServiceBase notifier) { _notifier = notifier; } - public async Task<(string CSharp, string Html, string Json)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) + public async Task<(string CSharp, string Html, string Json)> GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot) { var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("/")); var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? ""); @@ -145,7 +145,7 @@ public override ProjectSnapshotManagerBase Instance { //PROTOTYPE: it's here we could inject a 'host outputs retrieval' service new RemoteProjectSnapshotProjectEngineFactory(_optionsMonitor) - , new LspHostOutput(_notifierService) + , new LspRazorGeneratedDocumentProvider(_notifierService) }); _instance = new DefaultProjectSnapshotManager( _projectSnapshotManagerDispatcher, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectWorkspaceStateGenerator.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectWorkspaceStateGenerator.cs index 066fc47f2ec..79e8690442f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectWorkspaceStateGenerator.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectWorkspaceStateGenerator.cs @@ -59,7 +59,7 @@ public override void Initialize(ProjectSnapshotManagerBase projectManager) _projectManager = projectManager; _tagHelperResolver = _projectManager.Workspace.Services.GetRequiredService(); - var x = _projectManager.Workspace.Services.GetService(); + var x = _projectManager.Workspace.Services.GetService(); } public override void Update(Project workspaceProject, IProjectSnapshot projectSnapshot, CancellationToken cancellationToken) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/InProcRazorGeneratedDocumentProvider.cs similarity index 73% rename from src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/InProcRazorGeneratedDocumentProvider.cs index c6fba93fddf..ea750befffe 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorInProcGeneratorSnapshotDeely.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/InProcRazorGeneratedDocumentProvider.cs @@ -14,44 +14,41 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces; -[ExportWorkspaceServiceFactory(typeof(IGeneratorSnapshotProvider), ServiceLayer.Default)] -internal class DeelyFactory : IWorkspaceServiceFactory +[ExportWorkspaceServiceFactory(typeof(IRazorGeneratedDocumentProvider), ServiceLayer.Default)] +internal class InProcRazorGeneratedDocumentProviderFactory : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - //ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor = workspaceServices.GetRequiredService(workspaceServices); - return new RazorInProcGeneratorSnapshotDeely(workspaceServices.Workspace); + return new InProcRazorGeneratedDocumentProvider(workspaceServices.Workspace); } } -internal class RazorInProcGeneratorSnapshotDeely : IGeneratorSnapshotProvider +internal class InProcRazorGeneratedDocumentProvider : IRazorGeneratedDocumentProvider { private readonly Workspace _workspace; - public RazorInProcGeneratorSnapshotDeely(Workspace workspace) + public InProcRazorGeneratedDocumentProvider(Workspace workspace) { _workspace = workspace; } - public async Task<(string CSharp, string Html, string Json)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot) + public async Task<(string CSharp, string Html, string Json)> GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot) { string? csharp = null; string? html = null; string? json = null; - var project = _workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == documentSnapshot.Project.FilePath); if (project is not null) { - // PROTOTYPE: factor this out so we can share it + // PROTOTYPE: factor this out so we can share its var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("\\")); var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? ""); csharp = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg.cs", System.Threading.CancellationToken.None); html = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg.html", System.Threading.CancellationToken.None); - json = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg.json", System.Threading.CancellationToken.None); - + json = await RazorHostOutputHandler.GetHostOutputAsync(project, "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", documentName + ".rsg.json", System.Threading.CancellationToken.None); } return (csharp ?? "", html ?? "", json ?? ""); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs index 8e0d0f65b0d..166fe7d1f5b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratorSnapshotProvider.cs @@ -12,20 +12,7 @@ namespace Microsoft.CodeAnalysis.Razor; -internal interface IGeneratorSnapshotProvider : IWorkspaceService +internal interface IRazorGeneratedDocumentProvider : IWorkspaceService { - Task<(string CSharp, string Html, string Json)> GetGenerateDocumentsAsync(IDocumentSnapshot documentSnapshot); + Task<(string CSharp, string Html, string Json)> GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot); } - -//internal class GeneratorSnapshotProvider : IWorkspaceService -//{ -// public Task GetGeneratorSnapshotAsync(ProjectState project) -// { -// return Task.FromResult(new GeneratorSnapshot()); -// } -//} - -//internal class GeneratorSnapshot -//{ -// public RazorCodeDocument GetGeneratedSnapshot(string uri) { return null; } -//} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs index cce3b388c90..970757b2506 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs @@ -123,12 +123,12 @@ public virtual RazorProjectEngine GetProjectEngine() // PROTOTYPE: how do we handle getting the snapshot and not doing it multiple times? // probably need the TCS pattern thingy again - var snapshotService = State.Services.GetService(); + var snapshotService = State.Services.GetService(); //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message(this.State.Services.Workspace.CurrentSolution.Id.Id + HostProject.FilePath + ":Snaphost sevice is null: "+(snapshotService is null)); if (snapshotService is not null) { - var result = await snapshotService.GetGenerateDocumentsAsync(documentSnapshot); + var result = await snapshotService.GetGeneratedDocumentAsync(documentSnapshot); return result; } From 7aeabb95a921382f3141e87ac7869c6d48cde92a Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 9 May 2023 14:55:06 -0700 Subject: [PATCH 21/22] Factor out --- .../DefaultProjectSnapshotManagerAccessor.cs | 71 ---------------- .../LspRazorGeneratedDocumentProvider.cs | 80 +++++++++++++++++++ 2 files changed, 80 insertions(+), 71 deletions(-) create mode 100644 src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/LspRazorGeneratedDocumentProvider.cs diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs index d059390cef6..773da2d4b0a 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.Extensions.Options; -using Microsoft.CodeAnalysis.ExternalAccess.Razor.Remote; using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -20,73 +19,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer; -internal class LspRazorGeneratedDocumentProvider : IRazorGeneratedDocumentProvider -{ - readonly ClientNotifierServiceBase _notifier; - - public LspRazorGeneratedDocumentProvider(ClientNotifierServiceBase notifier) - { - _notifier = notifier; - } - - public async Task<(string CSharp, string Html, string Json)> GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot) - { - var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("/")); - var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? ""); - - var csharp = await RequestOutput(documentName + ".rsg.cs"); - var html = await RequestOutput(documentName + ".rsg.html"); - var json = await RequestOutput(documentName + ".rsg.json"); - - - return (csharp, html, json); - - async Task RequestOutput(string name) - { - var request = new HostOutputRequest() - { - TextDocument = new TextDocumentIdentifier() - { - Uri = new UriBuilder() - { - Scheme = Uri.UriSchemeFile, - Path = documentSnapshot.FilePath, - Host = string.Empty, - }.Uri - }, - GeneratorName = "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", - RequestedOutput = name, - }; - - var response = await _notifier.SendRequestAsync(RazorLanguageServerCustomMessageTargets.RazorHostOutputsEndpointName, request, CancellationToken.None); - return response.Output ?? string.Empty; - } - } - - //copied from the generator - internal static string GetIdentifierFromPath(string filePath) - { - var builder = new StringBuilder(filePath.Length); - - for (var i = 0; i < filePath.Length; i++) - { - switch (filePath[i]) - { - case ':' or '\\' or '/': - case char ch when !char.IsLetterOrDigit(ch): - builder.Append('_'); - break; - default: - builder.Append(filePath[i]); - break; - } - } - - return builder.ToString(); - } - -} - internal class DefaultProjectSnapshotManagerAccessor : ProjectSnapshotManagerAccessor, IDisposable { private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher; @@ -129,9 +61,6 @@ public DefaultProjectSnapshotManagerAccessor( _optionsMonitor = optionsMonitor; _workspaceFactory = workspaceFactory; _notifierService = notifier; - //_generatorSnapshotFactory = snapshotFactory; - - //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("project accessor made. notifier is :" + (notifier is not null)); } public override ProjectSnapshotManagerBase Instance diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/LspRazorGeneratedDocumentProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/LspRazorGeneratedDocumentProvider.cs new file mode 100644 index 00000000000..a6e221f2362 --- /dev/null +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/LspRazorGeneratedDocumentProvider.cs @@ -0,0 +1,80 @@ +// 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.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.LanguageServer.Common; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Remote; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.AspNetCore.Razor.LanguageServer; + +internal class LspRazorGeneratedDocumentProvider : IRazorGeneratedDocumentProvider +{ + readonly ClientNotifierServiceBase _notifier; + + public LspRazorGeneratedDocumentProvider(ClientNotifierServiceBase notifier) + { + _notifier = notifier; + } + + public async Task<(string CSharp, string Html, string Json)> GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot) + { + var projectRoot = documentSnapshot.Project.FilePath.Substring(0, documentSnapshot.Project.FilePath.LastIndexOf("/")); + var documentName = GetIdentifierFromPath(documentSnapshot.FilePath?.Substring(projectRoot.Length + 1) ?? ""); + + var csharp = await RequestOutput(documentName + ".rsg.cs"); + var html = await RequestOutput(documentName + ".rsg.html"); + var json = await RequestOutput(documentName + ".rsg.json"); + + return (csharp, html, json); + + async Task RequestOutput(string name) + { + var request = new HostOutputRequest() + { + TextDocument = new TextDocumentIdentifier() + { + Uri = new UriBuilder() + { + Scheme = Uri.UriSchemeFile, + Path = documentSnapshot.FilePath, + Host = string.Empty, + }.Uri + }, + GeneratorName = "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", + RequestedOutput = name, + }; + + var response = await _notifier.SendRequestAsync(RazorLanguageServerCustomMessageTargets.RazorHostOutputsEndpointName, request, CancellationToken.None); + return response.Output ?? string.Empty; + } + } + + //copied from the generator + internal static string GetIdentifierFromPath(string filePath) + { + var builder = new StringBuilder(filePath.Length); + + for (var i = 0; i < filePath.Length; i++) + { + switch (filePath[i]) + { + case ':' or '\\' or '/': + case char ch when !char.IsLetterOrDigit(ch): + builder.Append('_'); + break; + default: + builder.Append(filePath[i]); + break; + } + } + + return builder.ToString(); + } +} + From 7429cf686f3c88980de2d5167527f295ab371c95 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 9 May 2023 17:23:37 -0700 Subject: [PATCH 22/22] Cleanup --- ...soft.NET.Sdk.Razor.SourceGenerators.csproj | 1 - .../BoundAttributeDescriptorJsonConverter.cs | 180 ------------------ .../DefaultProjectSnapshotManagerAccessor.cs | 2 +- .../DefaultProjectSnapshotManager.cs | 8 - .../ProjectSystem/EphemeralProjectSnapshot.cs | 1 - .../ProjectSystem/IProjectSnapshot.cs | 2 - .../RazorLanguageServerClient.cs | 15 -- 7 files changed, 1 insertion(+), 208 deletions(-) delete mode 100644 src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs 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 ca9a0759543..54d554ab0a0 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 @@ -20,7 +20,6 @@ - diff --git a/src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs b/src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs deleted file mode 100644 index 3fea533c8da..00000000000 --- a/src/Compiler/shared/BoundAttributeDescriptorJsonConverter.cs +++ /dev/null @@ -1,180 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. - -//#nullable disable - -//using System; -//using System.Collections.Generic; -//using Microsoft.AspNetCore.Razor.Language; -//using Newtonsoft.Json; - -//namespace Microsoft.CodeAnalysis.Razor.Serialization; - -//internal class BoundAttributeDescriptorJsonConverter : JsonConverter -//{ -// public override bool CanConvert(Type objectType) -// { -// return typeof(BoundAttributeDescriptor).IsAssignableFrom(objectType); -// } - -// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) -// { -// BoundAttributeDescriptorBuilder builder = new DefaultBoundAttributeDescriptorBuilder(); -// R -// } - -// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) -// { -// } - -// public static void ReadBoundAttribute(JsonReader reader, BoundAttributeDescriptorBuilder attribute) -// { -// //reader.ReadProperties(propertyName => -// { -// switch (propertyName) -// { -// case nameof(BoundAttributeDescriptor.Name): -// if (reader.Read()) -// { -// var name = (string)reader.Value; -// attribute.Name = name; -// } -// break; -// case nameof(BoundAttributeDescriptor.TypeName): -// if (reader.Read()) -// { -// var typeName = (string)reader.Value; -// attribute.TypeName = typeName; -// } -// break; -// case nameof(BoundAttributeDescriptor.Documentation): -// if (reader.Read()) -// { -// var documentation = (string)reader.Value; -// attribute.Documentation = documentation; -// } -// break; -// case nameof(BoundAttributeDescriptor.IndexerNamePrefix): -// if (reader.Read()) -// { -// var indexerNamePrefix = (string)reader.Value; -// if (indexerNamePrefix != null) -// { -// attribute.IsDictionary = true; -// attribute.IndexerAttributeNamePrefix = indexerNamePrefix; -// } -// } -// break; -// case nameof(BoundAttributeDescriptor.IndexerTypeName): -// if (reader.Read()) -// { -// var indexerTypeName = (string)reader.Value; -// if (indexerTypeName != null) -// { -// attribute.IsDictionary = true; -// attribute.IndexerValueTypeName = indexerTypeName; -// } -// } -// break; -// case nameof(BoundAttributeDescriptor.IsEnum): -// if (reader.Read()) -// { -// var isEnum = (bool)reader.Value; -// attribute.IsEnum = isEnum; -// } -// break; -// case nameof(BoundAttributeDescriptor.IsEditorRequired): -// if (reader.Read()) -// { -// var value = (bool)reader.Value; -// attribute.IsEditorRequired = value; -// } -// break; -// case nameof(BoundAttributeDescriptor.BoundAttributeParameters): -// ReadBoundAttributeParameters(reader, attribute); -// break; -// case nameof(BoundAttributeDescriptor.Diagnostics): -// TagHelperDescriptorJsonConverter.ReadDiagnostics(reader, attribute.Diagnostics); -// break; -// case nameof(BoundAttributeDescriptor.Metadata): -// TagHelperDescriptorJsonConverter.ReadMetadata(reader, attribute.Metadata); -// break; -// } -// }); -// } - -// private static void ReadBoundAttributeParameters(JsonReader reader, BoundAttributeDescriptorBuilder builder) -// { -// if (!reader.Read()) -// { -// return; -// } - -// if (reader.TokenType != JsonToken.StartArray) -// { -// return; -// } - -// do -// { -// ReadBoundAttributeParameter(reader, builder); -// } while (reader.TokenType != JsonToken.EndArray); -// } - -// private static void ReadBoundAttributeParameter(JsonReader reader, BoundAttributeDescriptorBuilder builder) -// { -// if (!reader.Read()) -// { -// return; -// } - -// if (reader.TokenType != JsonToken.StartObject) -// { -// return; -// } - -// builder.BindAttributeParameter(parameter => -// { -// reader.ReadProperties(propertyName => -// { -// switch (propertyName) -// { -// case nameof(BoundAttributeParameterDescriptor.Name): -// if (reader.Read()) -// { -// var name = (string)reader.Value; -// parameter.Name = name; -// } -// break; -// case nameof(BoundAttributeParameterDescriptor.TypeName): -// if (reader.Read()) -// { -// var typeName = (string)reader.Value; -// parameter.TypeName = typeName; -// } -// break; -// case nameof(BoundAttributeParameterDescriptor.IsEnum): -// if (reader.Read()) -// { -// var isEnum = (bool)reader.Value; -// parameter.IsEnum = isEnum; -// } -// break; -// case nameof(BoundAttributeParameterDescriptor.Documentation): -// if (reader.Read()) -// { -// var documentation = (string)reader.Value; -// parameter.Documentation = documentation; -// } -// break; -// case nameof(BoundAttributeParameterDescriptor.Metadata): -// TagHelperDescriptorJsonConverter.ReadMetadata(reader, parameter.Metadata); -// break; -// case nameof(BoundAttributeParameterDescriptor.Diagnostics): -// TagHelperDescriptorJsonConverter.ReadDiagnostics(reader, parameter.Diagnostics); -// break; -// } -// }); -// }); -// } -//} diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs index 773da2d4b0a..52d1795b033 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultProjectSnapshotManagerAccessor.cs @@ -72,7 +72,7 @@ public override ProjectSnapshotManagerBase Instance var workspace = _workspaceFactory.Create( workspaceServices: new IWorkspaceService[] { - //PROTOTYPE: it's here we could inject a 'host outputs retrieval' service + //PROTOTYPE: is this the right place to inject the service? new RemoteProjectSnapshotProjectEngineFactory(_optionsMonitor) , new LspRazorGeneratedDocumentProvider(_notifierService) }); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs index d651163164d..dffe4e4b734 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs @@ -61,8 +61,6 @@ public DefaultProjectSnapshotManager( throw new ArgumentNullException(nameof(workspace)); } - //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Created dispatcher for workspace: "+workspace.CurrentSolution.Id.Id); - _projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher; _triggers = triggers.OrderByDescending(trigger => trigger.InitializePriority).ToArray(); Workspace = workspace; @@ -489,12 +487,6 @@ internal override void ProjectAdded(HostProject hostProject) return; } - //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("NewProject:" + hostProject.FilePath); - - // lets see if we can find it in the roslyn workspace - //var roslynProject = Workspace.CurrentSolution.Projects.SingleOrDefault(p => p.FilePath == hostProject.FilePath); - - var state = ProjectState.Create(Workspace.Services, hostProject); var entry = new Entry(state); _projects[hostProject.FilePath] = entry; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs index 1fd15f71507..8f180af5be9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectEngineHost.Serialization; using Microsoft.CodeAnalysis.CSharp; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IProjectSnapshot.cs index a8a71cdb056..728b323a0fc 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IProjectSnapshot.cs @@ -1,10 +1,8 @@ // 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.Generic; using System.Collections.Immutable; -using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectEngineHost.Serialization; using Microsoft.CodeAnalysis.CSharp; diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerClient.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerClient.cs index 15c369ad54e..acc57764b5a 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerClient.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/RazorLanguageServerClient.cs @@ -81,8 +81,6 @@ public RazorLanguageServerClient( //IRazorGeneratorSnapshotFactory razorGeneratorSnapshotFactory, [Import(AllowDefault = true)] VisualStudioHostServicesProvider? vsHostWorkspaceServicesProvider) { - //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Razor language server created"); - if (customTarget is null) { throw new ArgumentNullException(nameof(customTarget)); @@ -151,11 +149,6 @@ public RazorLanguageServerClient( _projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher; _telemetryReporter = telemetryReporter; _clientSettingsManager = clientSettingsManager; - //_razorGeneratorSnapshotFactory = razorGeneratorSnapshotFactory; - // _snapshotFactory = new GeneratorSnapshotFactory(workspace); - //Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Snapshot factory is not null: " + (_razorGeneratorSnapshotFactory is not null)); - - } public string Name => RazorLSPConstants.RazorLanguageServerName; @@ -279,14 +272,6 @@ private void ConfigureLanguageServer(IServiceCollection serviceCollection) var wrapper = new HostServicesProviderWrapper(_vsHostWorkspaceServicesProvider); serviceCollection.AddSingleton(wrapper); } - - //if (_razorGeneratorSnapshotFactory is not null) - //{ - // serviceCollection.AddSingleton(_razorGeneratorSnapshotFactory); - // Microsoft.CodeAnalysis.CodeAnalysisEventSource.Log.Message("Snapshot manager added to service collection"); - //} - - //serviceCollection.AddSingleton(_snapshotFactory); } private Trace GetVerbosity()