简体   繁体   English

使用Roslyn查找Visual Studio解决方案中的所有引用

[英]Find all references in Visual Studio solution using Roslyn

TLDR; TLDR;

How do I find all the const string parameters of the references to the index property Microsoft.Extensions.Localization.IStringLocalizer.Item[String] in my Visual Studio solution? 如何在Visual Studio解决方案中找到索引属性Microsoft.Extensions.Localization.IStringLocalizer.Item[String]的引用的所有const字符串参数? All source code is written in C#. 所有源代码都是用C#编写的。 The solution must also support MVC razor views. 该解决方案还必须支持MVC剃刀视图。

Additional info 附加信息

I believe that Roslyn is the answer to the question. 我相信罗斯林就是这个问题的答案。 I, however, haven't yet found my way through the API to achieve this. 但是,我还没有找到通过API来实现这一目标的方法。 I'm also uncertain about whether to use syntax tree, compilation or semantic model. 我还不确定是使用语法树,编译还是语义模型。 The following is an attempt based on other Q&A here on stackoverflow. 以下是基于stackoverflow的其他问答的尝试。 Any help to make it work is highly appreciated :-) If you are curious you can read about the reason for this need here . 任何有助于它的工作的帮助非常感谢:-)如果你很好奇,你可以在这里阅读这个需求的原因。

namespace AspNetCoreLocalizationKeysExtractor
{
    using System;
    using System.Linq;
    using Microsoft.CodeAnalysis.FindSymbols;
    using Microsoft.CodeAnalysis.MSBuild;

    class Program
    {
        static void Main(string[] args)
        {
            string solutionPath = @"..\source\MySolution.sln";
            var msWorkspace = MSBuildWorkspace.Create();

            var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;

            foreach (var project in solution.Projects.Where(p => p.AssemblyName.StartsWith("MyCompanyNamespace.")))
            {
                var compilation = project.GetCompilationAsync().Result;
                var interfaceType = compilation.GetTypeByMetadataName("Microsoft.Extensions.Localization.IStringLocalizer");

                // TODO: Find the indexer based on the name ("Item"/"this"?) and/or on the parameter and return type
                var indexer = interfaceType.GetMembers().First();

                var indexReferences = SymbolFinder.FindReferencesAsync(indexer, solution).Result.ToList();

                foreach (var symbol in indexReferences)
                {
                    // TODO: How to get output comprised by "a location" like e.g. a namespace qualified name and the parameter of the index call. E.g:
                    //
                    //   MyCompanyNamespace.MyLib.SomeClass: "Please try again"
                    //   MyCompanyNamespace.MyWebApp.Views.Shared._Layout: "Welcome to our cool website"
                    Console.WriteLine(symbol.Definition.ToDisplayString());
                }
            }
        }
    }
}

Update: Workaround 更新:解决方法

Despite the great help from @Oxoron I've chosen to resort to a simple workaround. 尽管@Oxoron提供了很大的帮助,但我选择采用简单的解决方法。 Currently Roslyn doesn't find any references using SymbolFinder.FindReferencesAsync . 目前,Roslyn没有使用SymbolFinder.FindReferencesAsync找到任何引用。 It appears to be according to "silent" msbuild failures. 它似乎是根据“沉默”的msbuild失败。 These errors are available like this: 这些错误可以这样:

msWorkspace.WorkspaceFailed += (sender, eventArgs) =>
{
  Console.Error.WriteLine($"{eventArgs.Diagnostic.Kind}: {eventArgs.Diagnostic.Message}");
  Console.Error.WriteLine();
};

and

var compilation = project.GetCompilationAsync().Result;
foreach (var diagnostic in compilation.GetDiagnostics())
  Console.Error.WriteLine(diagnostic);

My workaround is roughly like this: 我的解决方法大致如下:

public void ParseSource()
{
  var sourceFiles = from f in Directory.GetFiles(SourceDir, "*.cs*", SearchOption.AllDirectories)
                    where f.EndsWith(".cs") || f.EndsWith(".cshtml")
                    where !f.Contains(@"\obj\") && !f.Contains(@"\packages\")
                    select f;
  // _["Hello, World!"]
  // _[@"Hello, World!"]
  // _localizer["Hello, World!"]
  var regex = new Regex(@"_(localizer)?\[""(.*?)""\]");
  foreach (var sourceFile in sourceFiles)
  {
    foreach (var line in File.ReadLines(sourceFile))
    {
      var matches = regex.Matches(line);
      foreach (Match match in matches)
      {
        var resourceKey = GetResourceKeyFromFileName(sourceFile);
        var key = match.Groups[2].Value;
        Console.WriteLine($"{resourceKey}: {key}");
      }
    }
  }
}

Of course the solution isn't bullet proof and relies on naming conventions and doesn't handle multiline verbatim strings. 当然,该解决方案不是防弹,并且依赖于命名约定,并且不处理多行逐字字符串。 But it'll probably do the job for us :-) 但它可能会为我们做的工作:-)

Take a look on this and this questions, they will help with indexers. 看看这个这个问题,他们将帮助索引器。

Determine namespaces - it's a bit more difficult. 确定命名空间 - 这有点困难。 You can determine it using code like 你可以使用像这样的代码来确定它

int spanStart = symbol.Locations[0].Location.SourceSpan.Start;
Document doc = symbol.Locations[0].Location.Document;
var indexerInvokation = doc.GetSyntaxRootAsync().Result.DescendantNodes()
    .FirstOrDefault(node => node.GetLocation().SourceSpan.Start == spanStart );

After that just find indexerInvokation parents nodes until MethodDeclarationSyntax, ClassDeclarationSyntax, etc. 之后只需找到indexerInvokation父节点,直到MethodDeclarationSyntax,ClassDeclarationSyntax等。

Upd1. Upd1。 Test project code: 测试项目代码:

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            int test0 = new A().GetInt();
            int test1 = new IndexedUno()[2];
            int test2 = new IndexedDo()[2];
        }
    }

    public interface IIndexed
    {
        int this[int i] { get; }
    }


    public class IndexedUno : IIndexed
    {
        public int this[int i] => i;
    }

    public class IndexedDo : IIndexed
    {
        public int this[int i] => i;
    }

    public class A
    {
        public int GetInt() { return new IndexedUno()[1]; }
    }

    public class B
    {
        public int GetInt() { return new IndexedDo()[4]; }
    }
}

Search code: 搜索代码:

using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.MSBuild;

namespace AnalyzeIndexers
{
    class Program
    {
        static void Main(string[] args)
        {
            string solutionPath = @"PathToSolution.sln";
            var msWorkspace = MSBuildWorkspace.Create();
            var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;

            foreach (var project in solution.Projects.Where(p => p.AssemblyName.StartsWith("TestApp")))
            {
                var compilation = project.GetCompilationAsync().Result;
                var interfaceType = compilation.GetTypeByMetadataName("TestApp.IIndexed");
                var indexer = interfaceType
                    .GetMembers()
                    .OfType<IPropertySymbol>()
                    .First(member => member.IsIndexer);

                var indexReferences = SymbolFinder.FindReferencesAsync(indexer, solution).Result.ToList();

                foreach (var indexReference in indexReferences)
                {
                    foreach (ReferenceLocation indexReferenceLocation in indexReference.Locations)
                    {
                        int spanStart = indexReferenceLocation.Location.SourceSpan.Start;
                        var doc = indexReferenceLocation.Document;

                        var indexerInvokation = doc.GetSyntaxRootAsync().Result
                            .DescendantNodes()
                            .FirstOrDefault(node => node.GetLocation().SourceSpan.Start == spanStart);

                        var className = indexerInvokation.Ancestors()
                            .OfType<ClassDeclarationSyntax>()
                            .FirstOrDefault()
                            ?.Identifier.Text ?? String.Empty;

                        var @namespace = indexerInvokation.Ancestors()
                            .OfType<NamespaceDeclarationSyntax>()
                            .FirstOrDefault()
                            ?.Name.ToString() ?? String.Empty;


                        Console.WriteLine($"{@namespace}.{className} : {indexerInvokation.GetText()}");
                    }
                }
            }

            Console.WriteLine();
            Console.ReadKey();

        }
    }
}

Take a look at the var indexer = ... code - it extracts indexer from a type. 看一下var indexer = ...代码 - 它从一个类型中提取索引器。 Maybe you'll need to work with getter\\setter. 也许你需要使用getter \\ setter。

Other point of interest: indexerInvokation computation. 其他兴趣点:indexerInvokation计算。 We get SyntaxRoot too often, maybe you'll need some kind of cache. 我们经常得到SyntaxRoot,也许你需要某种缓存。

Next: class and namespace search. 下一页:类和命名空间搜索。 I didn't find a method, but recommend not to find it: there can be properties, other indexers, anonymous methods used your indexers. 我没有找到方法,但建议不要找到它:可以有属性,其他索引器,匿名方法使用你的索引器。 If you don't really care about this - just find ancestors of type MethodDeclarationSyntax. 如果你真的不关心这个 - 只需找到MethodDeclarationSyntax类型的祖先。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM