2017-09-04 169 views
2

TLDR;

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

附加信息

我相信罗斯林是这个问题的答案。然而,我还没有找到通过API实现这一目标的方法。我也不确定是使用语法树,编译还是语义模型。以下是基于其他Q & A在此基于stackoverflow的尝试。任何帮助,使其工作是高度赞赏:-)如果你很好奇,你可以阅读这个需要的原因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}"); 
     } 
    } 
    } 
} 

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

+0

Roslyn Analysys的第一条规则:编写你想要自动显示的代码。 你想查找类似localizer [“anyParam”]的代码吗? – Oxoron

+0

是的,正好@Oxoron :-)我不需要代码分析来处理除了用const字符串调用之外的任何其他特殊情况。 –

回答

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索引= ...代码 - 它从一个类型中提取索引。也许你需要使用getter \ setter。

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

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

+0

谢谢@Oxoron。我不认为这两个链接有帮助。我非常努力地获得索引器调用的符号。你知道这里的OfType泛型参数应该怎么做?或者我应该完全不同? 'var invocations = tree.GetRoot()。DescendantNodes()。OfType ();' –

+0

Thx again @Oxoron。你的更新中的例子似乎正是我所需要的。然而,我在这项任务上花费了很多时间,而且目前我的解决方案存在很多错误。 –

+0

这些错误似乎与使用VS和Roslyn使用的MSBuild构建解决方案的差异有关。唉,我已决定采取一种解决方法(请参阅我对问题的更新)。 但是,再次感谢您的帮助! –

相关问题