File: Handler\SourceGenerators\SourceGeneratedDocumentCache.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SourceGenerators;
 
internal record struct SourceGeneratedDocumentGetTextState(Document Document);
 
internal sealed class SourceGeneratedDocumentCache(string uniqueKey) : VersionedPullCache<(SourceGeneratorExecutionVersion, VersionStamp), object?, SourceGeneratedDocumentGetTextState, SourceText?>(uniqueKey), ILspService
{
    public override async Task<(SourceGeneratorExecutionVersion, VersionStamp)> ComputeCheapVersionAsync(SourceGeneratedDocumentGetTextState state, CancellationToken cancellationToken)
    {
        // The execution version and the dependent version must be considered as one version cached together -
        // it is not correct to say that if the execution version is the same then we can re-use results (as in automatic mode the execution version never changes).
        var executionVersion = state.Document.Project.Solution.GetSourceGeneratorExecutionVersion(state.Document.Project.Id);
        var dependentVersion = await state.Document.Project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false);
        return (executionVersion, dependentVersion);
    }
 
    public override Task<object?> ComputeExpensiveVersionAsync(SourceGeneratedDocumentGetTextState state, CancellationToken cancellationToken)
    {
        return SpecializedTasks.Null<object>();
    }
 
    public override Checksum ComputeChecksum(SourceText? data)
    {
        return data is null ? Checksum.Null : Checksum.From(data.GetChecksum());
    }
 
    public override async Task<SourceText?> ComputeDataAsync(SourceGeneratedDocumentGetTextState state, CancellationToken cancellationToken)
    {
        // When a user has a open source-generated file, we ensure that the contents in the LSP snapshot match the contents that we
        // get through didOpen/didChanges, like any other file. That way operations in LSP file are in sync with the
        // contents the user has. However in this case, we don't want to look at that frozen text, but look at what the
        // generator would generate if we ran it again. Otherwise, we'll get "stuck" and never update the file with something new.
        // This can return null when the source generated file has been removed (but the queue itself is using the frozen non-null document).
        var unfrozenDocument = await state.Document.Project.Solution.WithoutFrozenSourceGeneratedDocuments().GetDocumentAsync(state.Document.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
        return unfrozenDocument == null
            ? null
            : await unfrozenDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
    }
}
 
[ExportCSharpVisualBasicLspServiceFactory(typeof(SourceGeneratedDocumentCache)), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class SourceGeneratedDocumentCacheFactory() : ILspServiceFactory
{
    public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind)
    {
        return new SourceGeneratedDocumentCache(this.GetType().Name);
    }
}