File: Rename\IRemoteRenamerService.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Rename.ConflictEngine;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Rename;
 
internal interface IRemoteRenamerService
{
    /// <summary>
    /// Runs the entire rename operation OOP and returns the final result. More efficient (due to less back and
    /// forth marshaling) when the intermediary results of rename are not needed. To get the individual parts of
    /// rename remoted use <see cref="FindRenameLocationsAsync"/> and <see cref="ResolveConflictsAsync"/>.
    /// </summary>
    ValueTask<SerializableConflictResolution?> RenameSymbolAsync(
        Checksum solutionChecksum,
        SerializableSymbolAndProjectId symbolAndProjectId,
        string replacementText,
        SymbolRenameOptions options,
        ImmutableArray<SymbolKey> nonConflictSymbolKeys,
        CancellationToken cancellationToken);
 
    ValueTask<SerializableRenameLocations?> FindRenameLocationsAsync(
        Checksum solutionChecksum,
        SerializableSymbolAndProjectId symbolAndProjectId,
        SymbolRenameOptions options,
        CancellationToken cancellationToken);
 
    ValueTask<SerializableConflictResolution?> ResolveConflictsAsync(
        Checksum solutionChecksum,
        SerializableSymbolAndProjectId symbolAndProjectId,
        SerializableRenameLocations renameLocationSet,
        string replacementText,
        ImmutableArray<SymbolKey> nonConflictSymbolKeys,
        CancellationToken cancellationToken);
}
 
[DataContract]
internal readonly struct SerializableRenameLocation(
    TextSpan location,
    DocumentId documentId,
    CandidateReason candidateReason,
    bool isRenamableAliasUsage,
    bool isRenamableAccessor,
    TextSpan containingLocationForStringOrComment,
    bool isWrittenTo)
{
    [DataMember(Order = 0)]
    public readonly TextSpan Location = location;
 
    [DataMember(Order = 1)]
    public readonly DocumentId DocumentId = documentId;
 
    [DataMember(Order = 2)]
    public readonly CandidateReason CandidateReason = candidateReason;
 
    [DataMember(Order = 3)]
    public readonly bool IsRenamableAliasUsage = isRenamableAliasUsage;
 
    [DataMember(Order = 4)]
    public readonly bool IsRenamableAccessor = isRenamableAccessor;
 
    [DataMember(Order = 5)]
    public readonly TextSpan ContainingLocationForStringOrComment = containingLocationForStringOrComment;
 
    [DataMember(Order = 6)]
    public readonly bool IsWrittenTo = isWrittenTo;
 
    public static SerializableRenameLocation Dehydrate(RenameLocation location)
        => new(location.Location.SourceSpan,
               location.DocumentId,
               location.CandidateReason,
               location.IsRenamableAliasUsage,
               location.IsRenamableAccessor,
               location.ContainingLocationForStringOrComment,
               location.IsWrittenTo);
 
    public async ValueTask<RenameLocation> RehydrateAsync(Solution solution, CancellationToken cancellation)
    {
        var document = solution.GetRequiredDocument(DocumentId);
        var tree = await document.GetRequiredSyntaxTreeAsync(cancellation).ConfigureAwait(false);
 
        return new RenameLocation(
            CodeAnalysis.Location.Create(tree, Location),
            DocumentId,
            CandidateReason,
            IsRenamableAliasUsage,
            IsRenamableAccessor,
            IsWrittenTo,
            ContainingLocationForStringOrComment);
    }
}
 
internal sealed partial class LightweightRenameLocations
{
    public SerializableRenameLocations Dehydrate()
        => new(
            Options,
            Locations.SelectAsArray(SerializableRenameLocation.Dehydrate),
            _implicitLocations,
            _referencedSymbols);
}
 
internal sealed partial class SymbolicRenameLocations
{
    internal static async Task<SymbolicRenameLocations?> TryRehydrateAsync(
        ISymbol symbol, Solution solution, SerializableRenameLocations serializableLocations, CancellationToken cancellationToken)
    {
        Contract.ThrowIfNull(serializableLocations);
 
        var locations = await serializableLocations.Locations.SelectAsArrayAsync(
            static (loc, solution, cancellationToken) => loc.RehydrateAsync(solution, cancellationToken), solution, cancellationToken).ConfigureAwait(false);
 
        var implicitLocations = await serializableLocations.ImplicitLocations.SelectAsArrayAsync(
        static (loc, solution, cancellationToken) => loc.RehydrateAsync(solution, cancellationToken), solution, cancellationToken).ConfigureAwait(false);
 
        var referencedSymbols = await serializableLocations.ReferencedSymbols.SelectAsArrayAsync(
            static (sym, solution, cancellationToken) => sym.TryRehydrateAsync(solution, cancellationToken), solution, cancellationToken).ConfigureAwait(false);
        if (referencedSymbols.Any(s => s is null))
            return null;
 
        return new SymbolicRenameLocations(
            symbol,
            solution,
            serializableLocations.Options,
            locations,
            implicitLocations,
            referencedSymbols);
    }
}
 
[DataContract]
internal sealed class SerializableRenameLocations(
    SymbolRenameOptions options,
    ImmutableArray<SerializableRenameLocation> locations,
    ImmutableArray<SerializableReferenceLocation> implicitLocations,
    ImmutableArray<SerializableSymbolAndProjectId> referencedSymbols)
{
    [DataMember(Order = 0)]
    public readonly SymbolRenameOptions Options = options;
 
    [DataMember(Order = 1)]
    public readonly ImmutableArray<SerializableRenameLocation> Locations = locations;
 
    [DataMember(Order = 2)]
    public readonly ImmutableArray<SerializableReferenceLocation> ImplicitLocations = implicitLocations;
 
    [DataMember(Order = 3)]
    public readonly ImmutableArray<SerializableSymbolAndProjectId> ReferencedSymbols = referencedSymbols;
 
    public async ValueTask<ImmutableArray<RenameLocation>> RehydrateLocationsAsync(
        Solution solution, CancellationToken cancellationToken)
    {
        var locBuilder = new FixedSizeArrayBuilder<RenameLocation>(this.Locations.Length);
        foreach (var loc in this.Locations)
            locBuilder.Add(await loc.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false));
 
        return locBuilder.MoveToImmutable();
    }
}
 
[DataContract]
internal sealed class SerializableConflictResolution(string? errorMessage, SuccessfulConflictResolution? resolution)
{
    [DataMember(Order = 0)]
    public readonly string? ErrorMessage = errorMessage;
 
    [DataMember(Order = 1)]
    public readonly SuccessfulConflictResolution? Resolution = resolution;
 
    public async Task<ConflictResolution> RehydrateAsync(Solution oldSolution, CancellationToken cancellationToken)
    {
        if (ErrorMessage != null)
            return new ConflictResolution(ErrorMessage);
 
        Contract.ThrowIfNull(Resolution);
 
        var newSolutionWithoutRenamedDocument = await RemoteUtilities.UpdateSolutionAsync(
            oldSolution, Resolution.DocumentTextChanges, cancellationToken).ConfigureAwait(false);
 
        return new ConflictResolution(
            oldSolution,
            newSolutionWithoutRenamedDocument,
            Resolution.ReplacementTextValid,
            Resolution.RenamedDocument,
            Resolution.DocumentIds,
            Resolution.RelatedLocations,
            Resolution.DocumentToModifiedSpansMap,
            Resolution.DocumentToComplexifiedSpansMap,
            Resolution.DocumentToRelatedLocationsMap);
    }
}
 
[DataContract]
internal sealed class SuccessfulConflictResolution(
    bool replacementTextValid,
    (DocumentId documentId, string newName) renamedDocument,
    ImmutableArray<DocumentId> documentIds,
    ImmutableArray<RelatedLocation> relatedLocations,
    ImmutableArray<(DocumentId, ImmutableArray<TextChange>)> documentTextChanges,
    ImmutableDictionary<DocumentId, ImmutableArray<(TextSpan oldSpan, TextSpan newSpan)>> documentToModifiedSpansMap,
    ImmutableDictionary<DocumentId, ImmutableArray<ComplexifiedSpan>> documentToComplexifiedSpansMap,
    ImmutableDictionary<DocumentId, ImmutableArray<RelatedLocation>> documentToRelatedLocationsMap)
{
    [DataMember(Order = 0)]
    public readonly bool ReplacementTextValid = replacementTextValid;
 
    [DataMember(Order = 1)]
    public readonly (DocumentId documentId, string newName) RenamedDocument = renamedDocument;
 
    [DataMember(Order = 2)]
    public readonly ImmutableArray<DocumentId> DocumentIds = documentIds;
 
    [DataMember(Order = 3)]
    public readonly ImmutableArray<RelatedLocation> RelatedLocations = relatedLocations;
 
    [DataMember(Order = 4)]
    public readonly ImmutableArray<(DocumentId, ImmutableArray<TextChange>)> DocumentTextChanges = documentTextChanges;
 
    [DataMember(Order = 5)]
    public readonly ImmutableDictionary<DocumentId, ImmutableArray<(TextSpan oldSpan, TextSpan newSpan)>> DocumentToModifiedSpansMap = documentToModifiedSpansMap;
 
    [DataMember(Order = 6)]
    public readonly ImmutableDictionary<DocumentId, ImmutableArray<ComplexifiedSpan>> DocumentToComplexifiedSpansMap = documentToComplexifiedSpansMap;
 
    [DataMember(Order = 7)]
    public readonly ImmutableDictionary<DocumentId, ImmutableArray<RelatedLocation>> DocumentToRelatedLocationsMap = documentToRelatedLocationsMap;
}
 
internal partial struct ConflictResolution
{
    public async Task<SerializableConflictResolution> DehydrateAsync(CancellationToken cancellationToken)
    {
        if (!IsSuccessful)
            return new SerializableConflictResolution(ErrorMessage, resolution: null);
 
        var documentTextChanges = await RemoteUtilities.GetDocumentTextChangesAsync(OldSolution, _newSolutionWithoutRenamedDocument, cancellationToken).ConfigureAwait(false);
        return new SerializableConflictResolution(
            errorMessage: null,
            new SuccessfulConflictResolution(
                ReplacementTextValid,
                _renamedDocument.Value,
                DocumentIds,
                RelatedLocations,
                documentTextChanges,
                _documentToModifiedSpansMap,
                _documentToComplexifiedSpansMap,
                _documentToRelatedLocationsMap));
    }
}