Using Roslyn to analyze and rewrite code in a solution

 
 
  • Gérald Barré

I've written a lot about Roslyn in the context of Roslyn Analyzers and Source Generators. You can also use Roslyn as a library to analyze and generate code. For instance, you can create a console application that loads a solution, find patterns, and rewrite code. While Roslyn Analyzers are tied to a single project, you can use Roslyn as a library to analyze a whole solution.

Let's create the console application and add the necessary NuGet packages:

C#
dotnet new console
dotnet add package Microsoft.Build.Locator
dotnet add package Microsoft.CodeAnalysis.CSharp
dotnet add package Microsoft.CodeAnalysis.CSharp.Workspaces
dotnet add package Microsoft.CodeAnalysis.Workspaces.Common
dotnet add package Microsoft.CodeAnalysis.Workspaces.MSBuild

Here's a simple example of how to create a Roslyn workspace from a solution:

C#
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;

// Find where the MSBuild assemblies are located on your system.
// If you need a specific version, you can use MSBuildLocator.RegisterMSBuildPath.
Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();

// Note that you may need to restore the NuGet packages for the solution before opeing it with Roslyn.
// Depending on what you want to do, dependencies may be required for a correct analysis.

// Create a Roslyn workspace and load the solution
var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(@"my_solution.sln");

// You can now do whatever you want with the solution

Here're some examples of what you can do using Roslyn:

  • Analyze all syntax trees in a solution
C#
foreach (var project in solution.Projects)
{
    foreach (var document in project.Documents)
    {
        var syntaxTree = await document.GetSyntaxTreeAsync();
        var semanticModel = await document.GetSemanticModelAsync();
        // Analyze the syntax tree
        // Tip: You can use a CSharpSyntaxWalker to easily traverse the syntax tree
    }
}
  • Get all references to a symbol
C#
var compilation = await solution.Projects.First().GetCompilationAsync();
var stringSymbol = compilation.GetSpecialType(SpecialType.System_String);
var references = await SymbolFinder.FindReferencesAsync(stringSymbol, solution);
  • Rename a symbol
C#
var symbol = compilation.GetSymbolsWithName(s => s == "MyMethod").Single();
var newSolution = await Renamer.RenameSymbolAsync(solution, symbol, new SymbolRenameOptions() { RenameOverloads = true }, "MyMethod2");
workspace.TryApplyChanges(newSolution);
  • Update a document using DocumentEditor
C#
foreach (var project in solution.Projects)
{
    foreach (var document in project.Documents)
    {
        var editor = await DocumentEditor.CreateAsync(document);

        // Edit document
        foreach(var emptyStatement in editor.OriginalRoot.DescendantNodes().OfType<EmptyStatementSyntax>())
        {
            editor.RemoveNode(emptyStatement);
        }

        // Apply changes
        var newDocument = editor.GetChangedDocument();
        if (!workspace.TryApplyChanges(newDocument.Project.Solution))
        {
            Console.WriteLine("Failed to apply changes");
        }
    }
}
  • Update a document using CSharpSyntaxRewriter
C#
foreach (var project in solution.Projects)
{
    foreach (var document in project.Documents)
    {
        // Edit document
        var root = await document.GetSyntaxRootAsync();
        if(root is null)
            continue;

        var newRoot = new CustomRewriter().Visit(root);
        var newDocument = document.WithSyntaxRoot(newRoot);

        // Apply changes
        if (!workspace.TryApplyChanges(newDocument.Project.Solution))
        {
            Console.WriteLine("Failed to apply changes");
        }
    }
}

internal sealed class CustomRewriter : CSharpSyntaxRewriter
{
    public override SyntaxNode? VisitIfStatement(IfStatementSyntax node)
        => node.WithLeadingTrivia(SyntaxFactory.ParseLeadingTrivia("/* comment */"));
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub