File: Services\ConvertTupleToStructCodeRefactoringProvider\RemoteConvertTupleToStructCodeRefactoringService.cs
Web Access
Project: src\src\Workspaces\Remote\ServiceHub\Microsoft.CodeAnalysis.Remote.ServiceHub.csproj (Microsoft.CodeAnalysis.Remote.ServiceHub)
// 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.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.ConvertTupleToStruct;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Remote;
 
internal sealed class RemoteConvertTupleToStructCodeRefactoringService(in BrokeredServiceBase.ServiceConstructionArguments arguments)
    : BrokeredServiceBase(arguments), IRemoteConvertTupleToStructCodeRefactoringService
{
    internal sealed class Factory : FactoryBase<IRemoteConvertTupleToStructCodeRefactoringService>
    {
        protected override IRemoteConvertTupleToStructCodeRefactoringService CreateService(in ServiceConstructionArguments arguments)
            => new RemoteConvertTupleToStructCodeRefactoringService(arguments);
    }
 
    public ValueTask<SerializableConvertTupleToStructResult> ConvertToStructAsync(
        Checksum solutionChecksum,
        DocumentId documentId,
        TextSpan span,
        Scope scope,
        bool isRecord,
        CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var document = solution.GetRequiredDocument(documentId);
 
            var service = document.GetRequiredLanguageService<IConvertTupleToStructCodeRefactoringProvider>();
 
            var updatedSolution = await service.ConvertToStructAsync(document, span, scope, isRecord, cancellationToken).ConfigureAwait(false);
 
            var cleanedSolution = await CleanupAsync(solution, updatedSolution, cancellationToken).ConfigureAwait(false);
 
            var documentTextChanges = await RemoteUtilities.GetDocumentTextChangesAsync(
                solution, cleanedSolution, cancellationToken).ConfigureAwait(false);
            var renamedToken = await GetRenamedTokenAsync(
                solution, cleanedSolution, cancellationToken).ConfigureAwait(false);
 
            return new SerializableConvertTupleToStructResult(documentTextChanges, renamedToken);
        }, cancellationToken);
    }
 
    private static async Task<(DocumentId, TextSpan)> GetRenamedTokenAsync(
        Solution oldSolution, Solution newSolution, CancellationToken cancellationToken)
    {
        var changes = newSolution.GetChangedDocuments(oldSolution);
 
        foreach (var docId in changes)
        {
            var document = newSolution.GetRequiredDocument(docId);
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var renamedToken = root.GetAnnotatedTokens(RenameAnnotation.Kind).FirstOrNull();
            if (renamedToken == null)
                continue;
 
            return (docId, renamedToken.Value.Span);
        }
 
        throw ExceptionUtilities.Unreachable();
    }
 
    private static async Task<Solution> CleanupAsync(Solution oldSolution, Solution newSolution, CancellationToken cancellationToken)
    {
        var changes = newSolution.GetChangedDocuments(oldSolution);
        var final = newSolution;
 
        var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync(
            source: changes,
            produceItems: static async (docId, callback, newSolution, cancellationToken) =>
            {
                var document = newSolution.GetRequiredDocument(docId);
 
                var options = await document.GetCodeCleanupOptionsAsync(cancellationToken).ConfigureAwait(false);
                var cleaned = await CodeAction.CleanupDocumentAsync(document, options, cancellationToken).ConfigureAwait(false);
 
                var cleanedRoot = await cleaned.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
                callback((docId, cleanedRoot));
            },
            args: newSolution,
            cancellationToken).ConfigureAwait(false);
 
        return newSolution.WithDocumentSyntaxRoots(changedDocuments);
    }
}