Using Roslyn to analyze and rewrite code in a solution
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!
Enjoy this blog?💖 Sponsor on GitHub