简体   繁体   中英

Find all references in Visual Studio solution using Roslyn

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? All source code is written in C#. The solution must also support MVC razor views.

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. 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. 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. Currently Roslyn doesn't find any references using SymbolFinder.FindReferencesAsync . It appears to be according to "silent" msbuild failures. 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.

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. Maybe you'll need to work with getter\\setter.

Other point of interest: indexerInvokation computation. We get SyntaxRoot too often, maybe you'll need some kind of cache.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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