Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cohost wireup #11412

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.Language;

namespace Microsoft.NET.Sdk.Razor.SourceGenerators
{
internal class RazorGeneratorResult(IReadOnlyList<TagHelperDescriptor> tagHelpers, ImmutableDictionary<string, (string hintName, RazorCodeDocument document)> documents)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internal class RazorGeneratorResult(IReadOnlyList<TagHelperDescriptor> tagHelpers, ImmutableDictionary<string, (string hintName, RazorCodeDocument document)> documents)
internal sealed class RazorGeneratorResult(IReadOnlyList<TagHelperDescriptor> tagHelpers, ImmutableDictionary<string, (string hintName, RazorCodeDocument document)> documents)

{
public IReadOnlyList<TagHelperDescriptor> TagHelpers => tagHelpers;

public RazorCodeDocument? GetCodeDocument(string physicalPath) => documents.TryGetValue(physicalPath, out var pair) ? pair.document : null;

public string? GetHintName(string physicalPath) => documents.TryGetValue(physicalPath, out var pair) ? pair.hintName : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators
{
public partial class RazorSourceGenerator
{
private static string GetIdentifierFromPath(string filePath)
internal static string GetIdentifierFromPath(string filePath)
{
var builder = new StringBuilder(filePath.Length);

Expand All @@ -30,6 +30,7 @@ private static string GetIdentifierFromPath(string filePath)
}
}

builder.Append(".g.cs");
return builder.ToString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
Expand All @@ -18,6 +19,8 @@ public partial class RazorSourceGenerator : IIncrementalGenerator
{
private static RazorSourceGeneratorEventSource Log => RazorSourceGeneratorEventSource.Log;

internal static bool UseRazorCohostServer { get; set; } = false;

// Testing usage only.
private readonly string? _testSuppressUniqueIds;

Expand All @@ -37,7 +40,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var compilation = context.CompilationProvider;

// determine if we should suppress this run and filter out all the additional files and references if so
var isGeneratorSuppressed = analyzerConfigOptions.CheckGlobalFlagSet("SuppressRazorSourceGenerator");
var isGeneratorSuppressed = analyzerConfigOptions.CheckGlobalFlagSet("SuppressRazorSourceGenerator").Select((suppress, _) => !UseRazorCohostServer && suppress);
var additionalTexts = context.AdditionalTextsProvider.EmptyOrCachedWhen(isGeneratorSuppressed, true);
var metadataRefs = context.MetadataReferencesProvider.EmptyOrCachedWhen(isGeneratorSuppressed, true);

Expand Down Expand Up @@ -147,7 +150,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

// When using the generator cache in the compiler it's possible to encounter metadata references that are different instances
// but ultimately represent the same underlying assembly. We compare the module version ids to determine if the references are the same
if (!compilationA.References.SequenceEqual(compilationB.References, new LambdaComparer<MetadataReference>((old, @new) =>
if (!compilationA.References.SequenceEqual(compilationB.References, new LambdaComparer<MetadataReference>((old, @new) =>
{
if (ReferenceEquals(old, @new))
{
Expand Down Expand Up @@ -290,11 +293,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
});
}

var csharpDocuments = processed(designTime: false)
var razorDocuments = processed(designTime: false)
.Select(static (pair, _) =>
{
var (filePath, document) = pair;
return (filePath, csharpDocument: document.CodeDocument.GetCSharpDocument());
return (hintName: GetIdentifierFromPath(filePath), codeDocument: document.CodeDocument);
})
.WithTrackingName("RazorDocuments");

var csharpDocuments = razorDocuments
.Select(static (pair, _) =>
{
var (hintName, document) = pair;
return (hintName, csharpDocument: document.GetCSharpDocument());
})
.WithLambdaComparer(static (a, b) =>
{
Expand All @@ -315,14 +326,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

context.RegisterImplementationSourceOutput(csharpDocumentsWithSuppressionFlag, static (context, pair) =>
{
var ((filePath, csharpDocument), isGeneratorSuppressed) = pair;
var ((hintName, csharpDocument), isGeneratorSuppressed) = pair;

// When the generator is suppressed, we may still have a lot of cached data for perf, but we don't want to actually add any of the files to the output
if (!isGeneratorSuppressed)
{
// 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);
foreach (var razorDiagnostic in csharpDocument.Diagnostics)
{
Expand All @@ -333,6 +341,21 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.AddSource(hintName, csharpDocument.Text);
}
});

var hostOutputs = razorDocuments
.Collect()
.Combine(allTagHelpers)
.WithTrackingName("HostOutputs");

#pragma warning disable RSEXPERIMENTAL004 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
context.RegisterHostOutput(hostOutputs, (context, pair) =>
#pragma warning restore RSEXPERIMENTAL004 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
{
var (documents, tagHelpers) = pair;

var documentDictionary = documents.Select(p => new KeyValuePair<string, (string, RazorCodeDocument)>(p.codeDocument.Source.FilePath!, (p.hintName, p.codeDocument))).ToImmutableDictionary();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var documentDictionary = documents.Select(p => new KeyValuePair<string, (string, RazorCodeDocument)>(p.codeDocument.Source.FilePath!, (p.hintName, p.codeDocument))).ToImmutableDictionary();
var documentDictionary = documents.Select(p => KeyValuePair.Create(p.codeDocument.Source.FilePath!, (p.hintName, p.codeDocument))).ToImmutableDictionary();

context.AddOutput(nameof(RazorGeneratorResult), new RazorGeneratorResult(tagHelpers, documentDictionary));
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ internal class DefaultLanguageServerFeatureOptions : LanguageServerFeatureOption

public override bool UseRazorCohostServer => false;

public override bool DisableRazorLanguageServer => false;

public override bool ForceRuntimeCodeGeneration => false;

public override bool UseNewFormattingEngine => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ internal class ConfigurableLanguageServerFeatureOptions : LanguageServerFeatureO
private readonly bool? _updateBuffersForClosedDocuments;
private readonly bool? _includeProjectKeyInGeneratedFilePath;
private readonly bool? _useRazorCohostServer;
private readonly bool? _disableRazorLanguageServer;
private readonly bool? _forceRuntimeCodeGeneration;
private readonly bool? _useNewFormattingEngine;

Expand All @@ -36,7 +35,6 @@ internal class ConfigurableLanguageServerFeatureOptions : LanguageServerFeatureO
public override bool UpdateBuffersForClosedDocuments => _updateBuffersForClosedDocuments ?? _defaults.UpdateBuffersForClosedDocuments;
public override bool IncludeProjectKeyInGeneratedFilePath => _includeProjectKeyInGeneratedFilePath ?? _defaults.IncludeProjectKeyInGeneratedFilePath;
public override bool UseRazorCohostServer => _useRazorCohostServer ?? _defaults.UseRazorCohostServer;
public override bool DisableRazorLanguageServer => _disableRazorLanguageServer ?? _defaults.DisableRazorLanguageServer;
public override bool ForceRuntimeCodeGeneration => _forceRuntimeCodeGeneration ?? _defaults.ForceRuntimeCodeGeneration;
public override bool UseNewFormattingEngine => _useNewFormattingEngine ?? _defaults.UseNewFormattingEngine;
public override bool SupportsSoftSelectionInCompletion => false;
Expand All @@ -61,7 +59,6 @@ public ConfigurableLanguageServerFeatureOptions(string[] args)
TryProcessBoolOption(nameof(UpdateBuffersForClosedDocuments), ref _updateBuffersForClosedDocuments, option, args, i);
TryProcessBoolOption(nameof(IncludeProjectKeyInGeneratedFilePath), ref _includeProjectKeyInGeneratedFilePath, option, args, i);
TryProcessBoolOption(nameof(UseRazorCohostServer), ref _useRazorCohostServer, option, args, i);
TryProcessBoolOption(nameof(DisableRazorLanguageServer), ref _disableRazorLanguageServer, option, args, i);
TryProcessBoolOption(nameof(ForceRuntimeCodeGeneration), ref _forceRuntimeCodeGeneration, option, args, i);
TryProcessBoolOption(nameof(UseNewFormattingEngine), ref _useNewFormattingEngine, option, args, i);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ internal abstract class LanguageServerFeatureOptions

public abstract bool UseRazorCohostServer { get; }

public abstract bool DisableRazorLanguageServer { get; }

/// <summary>
/// When enabled, design time code will not be generated. All tooling, except formatting, will be using runtime code generation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Composition;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.NET.Sdk.Razor.SourceGenerators;

namespace Microsoft.CodeAnalysis.Remote.Razor;

Expand All @@ -15,7 +16,13 @@ internal class RemoteLanguageServerFeatureOptions : LanguageServerFeatureOptions
{
private RemoteClientInitializationOptions _options = default;

public void SetOptions(RemoteClientInitializationOptions options) => _options = options;
public void SetOptions(RemoteClientInitializationOptions options)
{
_options = options;

// ensure the source generator is in the correct mode
RazorSourceGenerator.UseRazorCohostServer = options.UseRazorCohostServer;
}

public override bool SupportsFileManipulation => _options.SupportsFileManipulation;

Expand All @@ -39,8 +46,6 @@ internal class RemoteLanguageServerFeatureOptions : LanguageServerFeatureOptions

public override bool UseRazorCohostServer => _options.UseRazorCohostServer;

public override bool DisableRazorLanguageServer => throw new InvalidOperationException("This option has not been synced to OOP.");

public override bool ForceRuntimeCodeGeneration => _options.ForceRuntimeCodeGeneration;

public override bool UseNewFormattingEngine => _options.UseNewFormattingEngine;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.ProjectSystem;
using Microsoft.AspNetCore.Razor.Threading;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Text;

Expand All @@ -23,9 +21,8 @@ internal sealed class RemoteDocumentSnapshot : IDocumentSnapshot
public TextDocument TextDocument { get; }
public RemoteProjectSnapshot ProjectSnapshot { get; }

// TODO: Delete this field when the source generator is hooked up
private readonly AsyncLazy<Document> _lazyDocument;
private readonly AsyncLazy<RazorCodeDocument> _lazyCodeDocument;
private RazorCodeDocument? _codeDocument;
private Document? _generatedDocument;

public RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot projectSnapshot)
{
Expand All @@ -36,9 +33,6 @@ public RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot p

TextDocument = textDocument;
ProjectSnapshot = projectSnapshot;

_lazyDocument = AsyncLazy.Create(HACK_ComputeDocumentAsync);
_lazyCodeDocument = AsyncLazy.Create(ComputeGeneratedOutputAsync);
}

public string FileKind => FileKinds.GetFileKindFromFilePath(FilePath);
Expand Down Expand Up @@ -70,26 +64,20 @@ public bool TryGetTextVersion(out VersionStamp result)
=> TextDocument.TryGetTextVersion(out result);

public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? result)
=> _lazyCodeDocument.TryGetValue(out result);
=> (result = _codeDocument) is not null;

public ValueTask<RazorCodeDocument> GetGeneratedOutputAsync(CancellationToken cancellationToken)
public async ValueTask<RazorCodeDocument> GetGeneratedOutputAsync(CancellationToken cancellationToken)
{
if (TryGetGeneratedOutput(out var result))
if (_codeDocument is not null)
{
return new(result);
return _codeDocument;
}

return new(_lazyCodeDocument.GetValueAsync(cancellationToken));
}

private async Task<RazorCodeDocument> ComputeGeneratedOutputAsync(CancellationToken cancellationToken)
{
var projectEngine = await ProjectSnapshot.GetProjectEngineAsync(cancellationToken).ConfigureAwait(false);
var compilerOptions = ProjectSnapshot.SolutionSnapshot.SnapshotManager.CompilerOptions;
var document = await ProjectSnapshot.GetCodeDocumentAsync(this, cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Could not get the code document"); // TODO: what happens if we can't get the code document?

return await CompilationHelpers
.GenerateCodeDocumentAsync(this, projectEngine, compilerOptions, cancellationToken)
.ConfigureAwait(false);
// TODO: what do we do if we can't find the document?
return InterlockedOperations.Initialize(ref _codeDocument, document);
}

#if !FORMAT_FUSE
Expand All @@ -103,26 +91,6 @@ public async Task<RazorCodeDocument> GenerateDesignTimeOutputAsync(CancellationT
}
#endif

private async Task<Document> HACK_ComputeDocumentAsync(CancellationToken cancellationToken)
{
// TODO: A real implementation needs to get the SourceGeneratedDocument from the solution

var solution = TextDocument.Project.Solution;
var filePathService = ProjectSnapshot.SolutionSnapshot.SnapshotManager.FilePathService;
var generatedFilePath = filePathService.GetRazorCSharpFilePath(Project.Key, FilePath);
var generatedDocumentId = solution
.GetDocumentIdsWithFilePath(generatedFilePath)
.First(TextDocument.Project.Id, static (d, projectId) => d.ProjectId == projectId);

var generatedDocument = solution.GetRequiredDocument(generatedDocumentId);

var codeDocument = await GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);
var csharpSourceText = codeDocument.GetCSharpSourceText();

// HACK: We're not in the same solution fork as the LSP server that provides content for this document
return generatedDocument.WithText(csharpSourceText);
}

public IDocumentSnapshot WithText(SourceText text)
{
var id = TextDocument.Id;
Expand All @@ -135,22 +103,23 @@ public IDocumentSnapshot WithText(SourceText text)
return snapshotManager.GetSnapshot(newDocument);
}

public bool TryGetGeneratedDocument([NotNullWhen(true)] out Document? result)
=> _lazyDocument.TryGetValue(out result);

public ValueTask<Document> GetGeneratedDocumentAsync(CancellationToken cancellationToken)
public async ValueTask<Document> GetGeneratedDocumentAsync(CancellationToken cancellationToken)
{
if (TryGetGeneratedDocument(out var result))
if (_generatedDocument is not null)
{
return new(result);
return _generatedDocument;
}

return new(_lazyDocument.GetValueAsync(cancellationToken));
var generatedDocument = await ProjectSnapshot.GetGeneratedDocumentAsync(this, cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Could not get the generated document"); // TODO: what happens if we can't get the generated document?

return InterlockedOperations.Initialize(ref _generatedDocument, generatedDocument);
}

public ValueTask<SyntaxTree> GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken)
{
if (TryGetGeneratedDocument(out var document) &&
var document = _generatedDocument;
if (document is not null &&
document.TryGetSyntaxTree(out var tree))
{
return new(tree.AssumeNotNull());
Expand Down
Loading
Loading