File: Rename\ConflictResolution.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.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Rename.ConflictEngine;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Rename;
 
internal readonly partial struct ConflictResolution
{
    /// <summary>
    /// A flag indicate if the rename operation is successful or not.
    /// If this is false, the <see cref="ErrorMessage"/> would be with this resolution. All the other field or property would be <see langword="null"/> or empty.
    /// If this is true, the <see cref="ErrorMessage"/> would be null. All the other fields or properties would be valid.
    /// </summary>
    [MemberNotNullWhen(false, nameof(ErrorMessage))]
    [MemberNotNullWhen(true, nameof(_newSolutionWithoutRenamedDocument))]
    [MemberNotNullWhen(true, nameof(_renamedDocument))]
    [MemberNotNullWhen(true, nameof(OldSolution))]
    [MemberNotNullWhen(true, nameof(NewSolution))]
    public bool IsSuccessful { get; }
 
    public readonly string? ErrorMessage;
 
    private readonly Solution? _newSolutionWithoutRenamedDocument;
    private readonly (DocumentId documentId, string newName)? _renamedDocument;
 
    public readonly Solution? OldSolution;
 
    /// <summary>
    /// The final solution snapshot.  Including any renamed documents.
    /// </summary>
    public readonly Solution? NewSolution;
 
    public readonly bool ReplacementTextValid;
 
    /// <summary>
    /// The list of all document ids of documents that have been touched for this rename operation.
    /// </summary>
    public readonly ImmutableArray<DocumentId> DocumentIds;
 
    public readonly ImmutableArray<RelatedLocation> RelatedLocations;
 
    private readonly ImmutableDictionary<DocumentId, ImmutableArray<(TextSpan oldSpan, TextSpan newSpan)>> _documentToModifiedSpansMap;
    private readonly ImmutableDictionary<DocumentId, ImmutableArray<ComplexifiedSpan>> _documentToComplexifiedSpansMap;
    private readonly ImmutableDictionary<DocumentId, ImmutableArray<RelatedLocation>> _documentToRelatedLocationsMap;
 
    public ConflictResolution(string errorMessage)
    {
        IsSuccessful = false;
        ErrorMessage = errorMessage;
 
        _newSolutionWithoutRenamedDocument = null;
        _renamedDocument = null;
        OldSolution = null;
        NewSolution = null;
        ReplacementTextValid = false;
        DocumentIds = [];
        RelatedLocations = [];
        _documentToModifiedSpansMap = ImmutableDictionary<DocumentId, ImmutableArray<(TextSpan oldSpan, TextSpan newSpan)>>.Empty;
        _documentToComplexifiedSpansMap = ImmutableDictionary<DocumentId, ImmutableArray<ComplexifiedSpan>>.Empty;
        _documentToRelatedLocationsMap = ImmutableDictionary<DocumentId, ImmutableArray<RelatedLocation>>.Empty;
    }
 
    public ConflictResolution(
        Solution oldSolution,
        Solution newSolutionWithoutRenamedDocument,
        bool replacementTextValid,
        (DocumentId documentId, string newName) renamedDocument,
        ImmutableArray<DocumentId> documentIds, ImmutableArray<RelatedLocation> relatedLocations,
        ImmutableDictionary<DocumentId, ImmutableArray<(TextSpan oldSpan, TextSpan newSpan)>> documentToModifiedSpansMap,
        ImmutableDictionary<DocumentId, ImmutableArray<ComplexifiedSpan>> documentToComplexifiedSpansMap,
        ImmutableDictionary<DocumentId, ImmutableArray<RelatedLocation>> documentToRelatedLocationsMap)
    {
        IsSuccessful = true;
        ErrorMessage = null;
 
        OldSolution = oldSolution;
        _newSolutionWithoutRenamedDocument = newSolutionWithoutRenamedDocument;
        ReplacementTextValid = replacementTextValid;
        _renamedDocument = renamedDocument;
        DocumentIds = documentIds;
        RelatedLocations = relatedLocations;
        _documentToModifiedSpansMap = documentToModifiedSpansMap;
        _documentToComplexifiedSpansMap = documentToComplexifiedSpansMap;
        _documentToRelatedLocationsMap = documentToRelatedLocationsMap;
 
        NewSolution = _renamedDocument.Value.documentId == null
            ? _newSolutionWithoutRenamedDocument
            : _newSolutionWithoutRenamedDocument.WithDocumentName(_renamedDocument.Value.documentId, _renamedDocument.Value.newName);
    }
 
    public ImmutableArray<(TextSpan oldSpan, TextSpan newSpan)> GetComplexifiedSpans(DocumentId documentId)
        => _documentToComplexifiedSpansMap.TryGetValue(documentId, out var complexifiedSpans)
            ? complexifiedSpans.SelectAsArray(c => (c.OriginalSpan, c.NewSpan))
            : ImmutableArray<(TextSpan oldSpan, TextSpan newSpan)>.Empty;
 
    public ImmutableDictionary<TextSpan, TextSpan> GetModifiedSpanMap(DocumentId documentId)
    {
        var result = ImmutableDictionary.CreateBuilder<TextSpan, TextSpan>();
        if (_documentToModifiedSpansMap.TryGetValue(documentId, out var modifiedSpans))
        {
            foreach (var (oldSpan, newSpan) in modifiedSpans)
                result[oldSpan] = newSpan;
        }
 
        if (_documentToComplexifiedSpansMap.TryGetValue(documentId, out var complexifiedSpans))
        {
            foreach (var complexifiedSpan in complexifiedSpans)
            {
                foreach (var (oldSpan, newSpan) in complexifiedSpan.ModifiedSubSpans)
                    result[oldSpan] = newSpan;
            }
        }
 
        return result.ToImmutable();
    }
 
    public ImmutableArray<RelatedLocation> GetRelatedLocationsForDocument(DocumentId documentId)
        => _documentToRelatedLocationsMap.TryGetValue(documentId, out var result)
            ? result
            : [];
 
    internal TextSpan GetResolutionTextSpan(TextSpan originalSpan, DocumentId documentId)
    {
        if (_documentToModifiedSpansMap.TryGetValue(documentId, out var modifiedSpans))
        {
            var first = modifiedSpans.FirstOrNull(t => t.oldSpan == originalSpan);
            if (first.HasValue)
                return first.Value.newSpan;
        }
 
        if (_documentToComplexifiedSpansMap.TryGetValue(documentId, out var complexifiedSpans))
        {
            var first = complexifiedSpans.FirstOrNull(c => c.OriginalSpan.Contains(originalSpan));
            if (first.HasValue)
                return first.Value.NewSpan;
        }
 
        // The RenamedSpansTracker doesn't currently track unresolved conflicts for
        // unmodified locations.  If the document wasn't modified, we can just use the 
        // original span as the new span.
        return originalSpan;
    }
}