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 :-)