File: Rename\SymbolicRenameLocations.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.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Rename;
 
/// <summary>
/// Holds the Locations of a symbol that should be renamed, along with the symbol and Solution for the set. It is
/// considered 'heavy weight' because it holds onto large entities (like Symbols) and thus should not be marshaled
/// to/from a host to OOP.
/// </summary>
internal sealed partial class SymbolicRenameLocations
{
    public readonly Solution Solution;
    public readonly ISymbol Symbol;
    public readonly SymbolRenameOptions Options;
 
    public readonly ImmutableArray<RenameLocation> Locations;
    public readonly ImmutableArray<ReferenceLocation> ImplicitLocations;
    public readonly ImmutableArray<ISymbol> ReferencedSymbols;
 
    public SymbolicRenameLocations(
        ISymbol symbol,
        Solution solution,
        SymbolRenameOptions options,
        ImmutableArray<RenameLocation> locations,
        ImmutableArray<ReferenceLocation> implicitLocations,
        ImmutableArray<ISymbol> referencedSymbols)
    {
        Debug.Assert(locations.Distinct().Count() == locations.Length, "Locations should be unique");
        Contract.ThrowIfTrue(locations.IsDefault);
        Contract.ThrowIfTrue(implicitLocations.IsDefault);
        Contract.ThrowIfTrue(referencedSymbols.IsDefault);
 
        Solution = solution;
        Symbol = symbol;
        Options = options;
        Locations = locations;
        ReferencedSymbols = referencedSymbols;
        ImplicitLocations = implicitLocations;
    }
 
    /// <summary>
    /// Attempts to find all the locations to rename.  Will not cross any process boundaries to do this.
    /// </summary>
    public static async Task<SymbolicRenameLocations> FindLocationsInCurrentProcessAsync(
        ISymbol symbol, Solution solution, SymbolRenameOptions options, CancellationToken cancellationToken)
    {
        Contract.ThrowIfNull(symbol);
        using (Logger.LogBlock(FunctionId.Rename_AllRenameLocations, cancellationToken))
        {
            symbol = await RenameUtilities.FindDefinitionSymbolAsync(symbol, solution, cancellationToken).ConfigureAwait(false);
 
            // First, find the direct references just to the symbol being renamed.
            var originalSymbolResult = await AddLocationsReferenceSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false);
 
            // Next, find references to overloads, if the user has asked to rename those as well.
            var overloadsResult = options.RenameOverloads ? await GetOverloadsAsync(symbol, solution, cancellationToken).ConfigureAwait(false) :
                [];
 
            // Finally, include strings/comments if that's what the user wants.
            var (strings, comments) = await ReferenceProcessing.GetRenamableLocationsInStringsAndCommentsAsync(
                symbol,
                solution,
                originalSymbolResult.Locations,
                options.RenameInStrings,
                options.RenameInComments,
                cancellationToken).ConfigureAwait(false);
 
            using var _0 = ArrayBuilder<RenameLocation>.GetInstance(out var mergedLocations);
            using var _1 = ArrayBuilder<ISymbol>.GetInstance(out var mergedReferencedSymbols);
            using var _2 = ArrayBuilder<ReferenceLocation>.GetInstance(out var mergedImplicitLocations);
 
            var renameMethodGroupReferences = options.RenameOverloads || !RenameUtilities.GetOverloadedSymbols(symbol).Any();
            foreach (var result in overloadsResult.Concat(originalSymbolResult))
            {
                mergedLocations.AddRange(renameMethodGroupReferences
                    ? result.Locations
                    : result.Locations.Where(x => x.CandidateReason != CandidateReason.MemberGroup));
 
                mergedImplicitLocations.AddRange(result.ImplicitLocations);
                mergedReferencedSymbols.AddRange(result.ReferencedSymbols);
            }
 
            // Add string and comment locations to the merged hashset 
            // after adding in reference symbols. This allows any references
            // in comments to be resolved as proper references rather than
            // comment resolutions. See https://github.com/dotnet/roslyn/issues/54294
            mergedLocations.AddRange(strings.NullToEmpty());
            mergedLocations.AddRange(comments.NullToEmpty());
 
            mergedLocations.RemoveDuplicates();
 
            return new SymbolicRenameLocations(
                symbol, solution, options,
                mergedLocations.ToImmutable(),
                mergedImplicitLocations.ToImmutable(),
                mergedReferencedSymbols.ToImmutable());
        }
    }
 
    private static async Task<ImmutableArray<SearchResult>> GetOverloadsAsync(
        ISymbol symbol, Solution solution, CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<SearchResult>.GetInstance(out var overloadsResult);
 
        foreach (var overloadedSymbol in RenameUtilities.GetOverloadedSymbols(symbol))
            overloadsResult.Add(await AddLocationsReferenceSymbolsAsync(overloadedSymbol, solution, cancellationToken).ConfigureAwait(false));
 
        return overloadsResult.ToImmutableAndClear();
    }
 
    private static async Task<SearchResult> AddLocationsReferenceSymbolsAsync(
        ISymbol symbol,
        Solution solution,
        CancellationToken cancellationToken)
    {
        var locations = ImmutableHashSet.CreateBuilder<RenameLocation>();
        var referenceSymbols = await SymbolFinder.FindRenamableReferencesAsync(
            [symbol], solution, cancellationToken).ConfigureAwait(false);
 
        foreach (var referencedSymbol in referenceSymbols)
        {
            locations.AddAll(
                await ReferenceProcessing.GetRenamableDefinitionLocationsAsync(referencedSymbol.Definition, symbol, solution, cancellationToken).ConfigureAwait(false));
 
            locations.AddAll(
                await referencedSymbol.Locations.SelectManyInParallelAsync(
                    (l, c) => ReferenceProcessing.GetRenamableReferenceLocationsAsync(referencedSymbol.Definition, symbol, l, solution, c),
                    cancellationToken).ConfigureAwait(false));
        }
 
        var implicitLocations = referenceSymbols.SelectMany(refSym => refSym.Locations).Where(loc => loc.IsImplicit).ToImmutableArray();
        var referencedSymbols = referenceSymbols.Select(r => r.Definition).Where(r => !r.Equals(symbol)).ToImmutableArray();
 
        return new SearchResult(locations.ToImmutable(), implicitLocations, referencedSymbols);
    }
}