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

时间:2017-09-04 07:45:04

标签: c# visual-studio roslyn projects-and-solutions

TLDR;

如何在Visual Studio解决方案中找到索引属性Microsoft.Extensions.Localization.IStringLocalizer.Item[String]的引用的所有const字符串参数?所有源代码都是用C#编写的。该解决方案还必须支持MVC剃刀视图。

其他信息

我相信罗斯林就是这个问题的答案。但是,我还没有找到通过API来实现这一目标的方法。我还不确定是使用语法树,编译还是语义模型。以下是基于stackoverflow的其他Q& A的尝试。我们非常感谢任何使其成功的帮助:-)如果您感到好奇,可以阅读有关此需求的原因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());
                }
            }
        }
    }
}

更新:解决方法

尽管@Oxoron提供了很大帮助,但我选择采用简单的解决方法。目前,Roslyn未使用SymbolFinder.FindReferencesAsync找到任何引用。它似乎是根据“沉默”的msbuild失败。这些错误可以这样:

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

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

我的解决方法大致如下:

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}");
      }
    }
  }
}

当然,该解决方案不是防弹的,并且依赖于命名约定,并且不处理多行逐字字符串。但它可能会为我们做好工作: - )

1 个答案:

答案 0 :(得分:1)

查看thisthis个问题,他们会帮助索引者。

确定命名空间 - 这有点困难。 您可以使用

之类的代码来确定它
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 );

之后只需找到indexerInvokation父节点,直到MethodDeclarationSyntax,ClassDeclarationSyntax等。

<强> Upd1。 测试项目代码:

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]; }
    }
}

搜索代码:

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();

        }
    }
}

看一下var indexer = ...代码 - 它从一个类型中提取索引器。也许你需要使用getter \ setter。

其他兴趣点:indexerInvokation计算。我们经常得到SyntaxRoot,也许你需要某种缓存。

下一步:类和命名空间搜索。我没有找到方法,但建议不要找到它:可以有属性,其他索引器,匿名方法使用你的索引器。如果你真的不关心这个 - 只需找到MethodDeclarationSyntax类型的祖先。