// 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.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Rename
{
///
/// Holds the Locations of a symbol that should be renamed, along with the symbol and Solution
/// for the set.
///
internal sealed partial class RenameLocations
{
private class SearchResult
{
public readonly IEnumerable ImplicitLocations;
public readonly ISet Locations;
public readonly IEnumerable ReferencedSymbols;
public SearchResult(
ISet locations,
IEnumerable implicitLocations,
IEnumerable referencedSymbols)
{
this.Locations = locations;
this.ImplicitLocations = implicitLocations;
this.ReferencedSymbols = referencedSymbols;
}
}
// never null fields
private readonly SymbolAndProjectId _symbolAndProjectId;
private readonly Solution _solution;
private readonly SearchResult _mergedResult;
internal OptionSet Options { get; }
// possibly null fields
private readonly SearchResult _originalSymbolResult;
private readonly List _overloadsResult;
private readonly IEnumerable _stringsResult;
private readonly IEnumerable _commentsResult;
internal RenameLocations(
ISet locations,
SymbolAndProjectId symbolAndProjectId,
Solution solution,
IEnumerable referencedSymbols,
IEnumerable implicitLocations,
OptionSet options)
{
_symbolAndProjectId = symbolAndProjectId;
_solution = solution;
_mergedResult = new SearchResult(locations, implicitLocations, referencedSymbols);
Options = options;
}
private RenameLocations(
SymbolAndProjectId symbolAndProjectId,
Solution solution,
OptionSet options,
SearchResult originalSymbolResult,
List overloadsResult,
IEnumerable stringsResult,
IEnumerable commentsResult)
{
_symbolAndProjectId = symbolAndProjectId;
_solution = solution;
Options = options;
_originalSymbolResult = originalSymbolResult;
_overloadsResult = overloadsResult;
_stringsResult = stringsResult;
_commentsResult = commentsResult;
var mergedLocations = new HashSet();
var mergedReferencedSymbols = new List();
var mergedImplicitLocations = new List();
if (options.GetOption(RenameOptions.RenameInStrings))
{
mergedLocations.AddRange(stringsResult);
}
if (options.GetOption(RenameOptions.RenameInComments))
{
mergedLocations.AddRange(commentsResult);
}
var renameMethodGroupReferences =
options.GetOption(RenameOptions.RenameOverloads) || !GetOverloadedSymbols(symbolAndProjectId).Any();
var overloadsToMerge = (options.GetOption(RenameOptions.RenameOverloads) ? overloadsResult : null) ?? SpecializedCollections.EmptyEnumerable();
foreach (var result in overloadsToMerge.Concat(originalSymbolResult))
{
mergedLocations.AddRange(renameMethodGroupReferences
? result.Locations
: result.Locations.Where(x => x.CandidateReason != CandidateReason.MemberGroup));
mergedImplicitLocations.AddRange(result.ImplicitLocations);
mergedReferencedSymbols.AddRange(result.ReferencedSymbols);
}
_mergedResult = new SearchResult(mergedLocations, mergedImplicitLocations, mergedReferencedSymbols);
}
public ISet Locations => _mergedResult.Locations;
public SymbolAndProjectId SymbolAndProjectId => _symbolAndProjectId;
public ISymbol Symbol => _symbolAndProjectId.Symbol;
public Solution Solution => _solution;
public IEnumerable ReferencedSymbols => _mergedResult.ReferencedSymbols;
public IEnumerable ImplicitLocations => _mergedResult.ImplicitLocations;
///
/// Find the locations that need to be renamed.
///
internal static async Task FindAsync(
SymbolAndProjectId symbolAndProjectId, Solution solution, OptionSet optionSet, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(symbolAndProjectId.Symbol);
using (Logger.LogBlock(FunctionId.Rename_AllRenameLocations, cancellationToken))
{
symbolAndProjectId = await ReferenceProcessing.FindDefinitionSymbolAsync(symbolAndProjectId, solution, cancellationToken).ConfigureAwait(false);
var originalSymbolResult = await AddLocationsReferenceSymbolsAsync(symbolAndProjectId, solution, cancellationToken).ConfigureAwait(false);
var intermediateResult = new RenameLocations(symbolAndProjectId, solution, optionSet, originalSymbolResult, overloadsResult: null, stringsResult: null, commentsResult: null);
return await intermediateResult.FindWithUpdatedOptionsAsync(optionSet, cancellationToken).ConfigureAwait(false);
}
}
internal async Task FindWithUpdatedOptionsAsync(OptionSet optionSet, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(Options, "FindWithUpdatedOptionsAsync can only be called on a result of FindAsync");
using (Logger.LogBlock(FunctionId.Rename_AllRenameLocations, cancellationToken))
{
var overloadsResult = _overloadsResult ?? (optionSet.GetOption(RenameOptions.RenameOverloads)
? await GetOverloadsAsync(_symbolAndProjectId, _solution, cancellationToken).ConfigureAwait(false)
: null);
var stringsAndComments = await ReferenceProcessing.GetRenamableLocationsInStringsAndCommentsAsync(
_symbolAndProjectId.Symbol,
_solution,
_originalSymbolResult.Locations,
optionSet.GetOption(RenameOptions.RenameInStrings) && _stringsResult == null,
optionSet.GetOption(RenameOptions.RenameInComments) && _commentsResult == null,
cancellationToken).ConfigureAwait(false);
return new RenameLocations(
_symbolAndProjectId, _solution, optionSet, _originalSymbolResult,
_overloadsResult ?? overloadsResult,
_stringsResult ?? stringsAndComments.Item1,
_commentsResult ?? stringsAndComments.Item2);
}
}
private static async Task> GetOverloadsAsync(
SymbolAndProjectId symbolAndProjectId, Solution solution, CancellationToken cancellationToken)
{
var overloadsResult = new List();
foreach (var overloadedSymbol in GetOverloadedSymbols(symbolAndProjectId))
{
overloadsResult.Add(await AddLocationsReferenceSymbolsAsync(overloadedSymbol, solution, cancellationToken).ConfigureAwait(false));
}
return overloadsResult;
}
internal static IEnumerable GetOverloadedSymbols(
SymbolAndProjectId symbolAndProjectId)
{
var symbol = symbolAndProjectId.Symbol;
if (symbol is IMethodSymbol)
{
var containingType = symbol.ContainingType;
if (containingType.Kind == SymbolKind.NamedType)
{
foreach (var member in containingType.GetMembers())
{
if (string.Equals(member.MetadataName, symbol.MetadataName, StringComparison.Ordinal) && member is IMethodSymbol && !member.Equals(symbol))
{
yield return symbolAndProjectId.WithSymbol(member);
}
}
}
}
}
private static async Task AddLocationsReferenceSymbolsAsync(
SymbolAndProjectId symbolAndProjectId,
Solution solution,
CancellationToken cancellationToken)
{
var symbol = symbolAndProjectId.Symbol;
var locations = new HashSet();
var referenceSymbols = await SymbolFinder.FindRenamableReferencesAsync(
symbolAndProjectId, 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.SelectManyAsync(
(l, c) => ReferenceProcessing.GetRenamableReferenceLocationsAsync(referencedSymbol.Definition, symbol, l, solution, c),
cancellationToken).ConfigureAwait(false));
}
var implicitLocations = referenceSymbols.SelectMany(refSym => refSym.Locations).Where(loc => loc.IsImplicit).ToList();
var referencedSymbols = referenceSymbols.Select(r => r.DefinitionAndProjectId).Where(r => !r.Symbol.Equals(symbol)).ToList();
return new SearchResult(locations, implicitLocations, referencedSymbols);
}
public RenameLocations Filter(Func filter)
=> new RenameLocations(
this.Locations.Where(loc => filter(loc.Location)).ToSet(),
this.SymbolAndProjectId, this.Solution,
this.ReferencedSymbols, this.ImplicitLocations.Where(loc => filter(loc.Location)),
this.Options);
}
}