Roslyn 重构查找对方法的引用

问题描述 投票:0回答:1

作为我正在进行的重构项目的一部分,我需要用正确的调用替换对过时方法的调用。这对其他电话有效,但我目前的电话遇到了问题。

我需要替换对以下内容的所有调用:

[Obsolete("use string.IsNullOrWhiteSpace instead of this extension")]
public static bool IsNullOrWhiteSpace(this string s)
{
    return string.IsNullOrWhiteSpace(s);
}

使用框架中的简单

string.IsNullOrWhiteSpace(string here)
。运行时它确实起作用,因为它修复了它找到的所有实例。我无法解释的是它没有识别的 200 多个实例。

这里有两个它没有找到的例子,这个来自同一个班级。

public static string TrimToUpper(this string s)
{
    return !IsNullOrWhiteSpace(s) ? s.Trim().ToUpperInvariant() : s;
}

我想也许是因为直接调用了该方法,但就查找参考而言,这对我来说没有意义。如果在同一解决方案的项目的另一个类中,第二个示例很简单

if (enterpriseCode.IsNullOrWhiteSpace())
第一个过去检测到其他几个类似的实例,但没有检测到这个。

这是我用来查找对我希望替换的方法的所有引用的代码:

public sealed class ReferenceFinder
{  
    public ReferenceFinder(string solutionPath)
    {
        SolutionInfo = GetSolutionInfo(solutionPath).GetAwaiter().GetResult();
    } 
    public ReferenceTree RetreiveSolutionReferences(string typeName, string methodName)
        => new ReferenceTree(typeName, methodName, GetReferenceLocationsByProject(GetReferenceSymbols(GetTypeSymbols(typeName), methodName)));
    private IEnumerable<INamedTypeSymbol> GetTypeSymbols(string typeName)
        => SolutionInfo.ProjectInfo.Select(pi => pi.Compilation.GetTypeByMetadataName(typeName)).Where(x => x != null);
    private IEnumerable<ReferencedSymbol> FindConstructorReferences(INamedTypeSymbol symbol)
        => symbol.Constructors.SelectMany(c => SymbolFinder.FindReferencesAsync(c, SolutionInfo.Solution).Result);
    private IEnumerable<ReferencedSymbol> FindMethodReferences(INamedTypeSymbol symbol, string methodName)
        => symbol.GetMembers(methodName).SelectMany(m => SymbolFinder.FindReferencesAsync(m, SolutionInfo.Solution).Result);
    private IEnumerable<ReferencedSymbol> GetReferenceSymbols(IEnumerable<INamedTypeSymbol> symbols, string methodName)
        => symbols.SelectMany(x => x.Name == methodName ? FindConstructorReferences(x) : FindMethodReferences(x, methodName));
    private ILookup<Project, ReferenceLocation> GetReferenceLocationsByProject(IEnumerable<ReferencedSymbol> symbols)
        => symbols.SelectMany(x => x.Locations).Distinct().ToLookup(x => x.Document.Project);
    private async Task<SolutionInfo> GetSolutionInfo(string path)
    {
        using (var workspace = MSBuildWorkspace.Create())
        {
            var solution = await workspace.OpenSolutionAsync(path);
            var compilations = await Task.WhenAll(solution.Projects.Select(async x => (x, await x.GetCompilationAsync())).AsEnumerable());
            return new SolutionInfo((solution, compilations));
        }
    }

    public readonly SolutionInfo SolutionInfo;
}

我正在向 RetreiveSolutionReferencs 传递以下“DVWorkshop.StringExtensions”(要替换的方法的类和命名空间)“IsNullOrWhiteSpace”(要替换的方法)。

我什至写了一个简短的方法来尝试单步执行并查看正在获取哪些参考资料。

        public void GetReferencesForSpecificProject(string typeName, string methodName, string projectName)
    {
        var pi = SolutionInfo.ProjectInfo.First(x => x.Project.Name == projectName);
        var symbol = pi.Compilation.GetTypeByMetadataName(typeName);

        var members = symbol.GetMembers(methodName).ToList();
        var rList = new List<IEnumerable<ReferenceLocation>>();
        var cList = new List<IEnumerable<Location>>();
        foreach (var m in members)
        {
            var referenceLocations = SymbolFinder.FindReferencesAsync(m, SolutionInfo.Solution).Result.SelectMany(r => r.Locations);
            var callerLocations = SymbolFinder.FindCallersAsync(m, SolutionInfo.Solution).Result.SelectMany(r => r.Locations);

            rList.Add(referenceLocations);
            cList.Add(callerLocations);
        }
    }

我在参考和调用者集合中看到了几个位置,但还不够,而且它们都不是我在“应用程序”中传递的项目,即使我看到了调用并转到定义将我带到了我的方法。

是什么导致我错过了对我的方法的引用?

c# roslyn
1个回答
0
投票

在程序集中搜索在另一个程序集中声明的类型时,您需要从该方法的引用库中获取符号。例如:

如果程序集 A 有一个名为 MyClass 的 DoStuff() 方法,程序集 B 有一个创建 MyClass 实例并调用 DoStuff() 的方法。

您想从程序集 A 中获取符号并使用它来查找对 DoStuff() 的所有引用。当您在程序集 B 中查找 DoStuff() 时,您需要从程序集 B 中获取对同一方法的符号引用,然后在程序集 B 中搜索对该符号的引用。

public sealed class ReferenceFinder
{
    private ConcurrentDictionary<ReferenceLocation, object> processedReferenceLocations = new ConcurrentDictionary<ReferenceLocation, object>();
    public ReferenceFinder(string solutionPath) =>
        Solution = SetSolutionInfo(solutionPath).GetAwaiter().GetResult();
    public ReferenceTree GetReferenceTree(string path, string typeName, string methodName) =>
        new ReferenceTree(methodName, path, GetNodeFromSymbol(typeName), GetReferenceNodes(GetReferenceSymbols(GetTypeSymbols(typeName), methodName)).SelectMany(x => GetSymbolBySyntaxType<MethodDeclarationSyntax>(x).Concat(GetSymbolBySyntaxType<ConstructorDeclarationSyntax>(x))).ToList());
    private SyntaxNode GetNodeFromSymbol(string typeName) =>
        (GetTypeSymbols(typeName).FirstOrDefault() != null) ? (GetTypeSymbols(typeName).First() as ISymbol).DeclaringSyntaxReferences.First()?.SyntaxTree.GetRoot() : null;
    private IEnumerable<INamedTypeSymbol> GetTypeSymbols(string typeName) =>
        Compilations.Values.Select(comp => comp.GetTypeByMetadataName(typeName))                                                    //Get NamedTypeSymbols from each project
        .Where(nts => nts != null);                                                                                                 //Where the symbol isn't null
    private IEnumerable<ReferencedSymbol> GetReferenceSymbols(IEnumerable<INamedTypeSymbol> symbols, string methodName) =>
        symbols.Concat(symbols.SelectMany(s => s.GetMembers(methodName)))                                                           //Concat the symbol with its member symbols
        .SelectMany(s => SymbolFinder.FindReferencesAsync(s, Solution).Result);                                                     //Get the ReferencedSymbols for each NamedTypeSymbol  
    private IEnumerable<(ReferenceLocation, IEnumerable<SyntaxNode>)> GetReferenceNodes(IEnumerable<ReferencedSymbol> symbols)
       => symbols.SelectMany(refSymbol => refSymbol.Locations                                                                       //Get Locations for all the Symbols               
           .Where(refLocation => processedReferenceLocations.TryAdd(refLocation, null))).AsParallel().WithDegreeOfParallelism(10)   //That have not been processed before                                                                             
          .Select(rl => (rl, rl.Location.SourceTree.GetRoot().FindToken(rl.Location.SourceSpan.Start).Parent.AncestorsAndSelf()));  //Return Location and all referencing nodes         
    
    // This returns all of the reference information for a particular node type (Method or Constructor)
    private IEnumerable<(ISymbol symbol, SyntaxNode node, ReferenceLocation location, string method)> GetSymbolBySyntaxType<T>((ReferenceLocation Location, IEnumerable<SyntaxNode> Nodes) locationAndNodes) where T : SyntaxNode
    {
        var referencesOfType = locationAndNodes.Nodes.OfType<T>();
        var symbol = referencesOfType.Any() ? Compilations[locationAndNodes.Location.Document.Project.Name].GetSemanticModel(locationAndNodes.Location.Location.SourceTree.GetRoot().SyntaxTree).GetDeclaredSymbol(referencesOfType.First()) : null;

        foreach (var reference in referencesOfType)
        {
            // Both MethodDeclarationSyntax and ConstructorDeclarationSyntax have an Identifier Property that is needed.  The property however is class level and not
            // shared by the inheritance implementations, thus they must be tested individually instead of referred to as a base type.
            var method = (reference is MethodDeclarationSyntax) ? (reference as MethodDeclarationSyntax).Identifier.ValueText : (reference as ConstructorDeclarationSyntax).Identifier.ValueText;
            yield return (symbol, reference, locationAndNodes.Location, method);
        }
    }
    private async Task<Solution> SetSolutionInfo(string path)
    {
        using (var workspace = MSBuildWorkspace.Create())
        {
            var solution = await workspace.OpenSolutionAsync(path);
            var compilations = await Task.WhenAll(solution.Projects.AsParallel().WithDegreeOfParallelism(10).Select(async x => (x, await x.GetCompilationAsync())).AsEnumerable());
            Compilations = compilations.ToDictionary(x => x.x.Name, x => x.Item2);
            return solution;
        }
    }
    private Solution Solution;
    private Dictionary<string, Compilation> Compilations = new Dictionary<string, Compilation>();
}

然后在你的调用类中:

    private static IEnumerable<ReferenceTree> FindReferences(ReferenceFinder finder, ReferenceTree woods)
    {
        // Yield the Method Reference itself
        yield return woods;

        // For each reference to this reference
        foreach (var tree in woods.References.AsParallel().WithDegreeOfParallelism(10).Select(x =>
                                                    finder.GetReferenceTree(x.Location.Document.FilePath,
                                                    x.Symbol.GetQualifiedClassName(),
                                                    x.Node is MethodDeclarationSyntax ? (x.Node as MethodDeclarationSyntax).Identifier.ValueText :
                                                    (x.Node as ConstructorDeclarationSyntax).Identifier.ValueText))
                                                .Distinct())
        {
            // Yield the immediate child references
            yield return tree;

            // Recursively get and yield all referencing methods  
            foreach (var branch in tree.References.AsParallel().WithDegreeOfParallelism(10)
                                .SelectMany(x => FindReferences(
                                        finder,
                                        finder.GetReferenceTree(
                                                x.Location.Document.FilePath,
                                                x.Symbol.GetQualifiedClassName(),
                                                x.Node is MethodDeclarationSyntax ? (x.Node as MethodDeclarationSyntax).Identifier.ValueText :
                                                (x.Node as ConstructorDeclarationSyntax).Identifier.ValueText))))
            {
                yield return branch;
            }
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.