In this chapter you are going to walk through a syntax tree using the visitor pattern.
Prerequisites
For this chapter we need Visual Studio 2019 with the .NET Compiler Platform SDK installed.
Make sure to choose the .NET Core version.
Name the project RoslynCompileSourceCode
.
The default values should be sufficient, make sure you store the project in an easy-to-reach place on disk.
Add a reference to the Microsoft.CodeAnalysis.CSharp
NuGet package.
Console
❯ dotnet add package "Microsoft.CodeAnalysis.CSharp"
On top of the file, add the following namespaces.
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Add all code to the Main
method.
With CSharpSyntaxTree.ParseText
you can parse text into a syntax tree.
For this application there is a prepared piece of source code you are going to use.
SyntaxTree tree = CSharpSyntaxTree.ParseText(@"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace TopLevel
{
using Microsoft;
using System.ComponentModel;
namespace Child1
{
using Microsoft.Win32;
using System.Runtime.InteropServices;
class Foo { }
}
namespace Child2
{
using System.CodeDom;
using Microsoft.CSharp;
class Bar { }
}
}");
You can see there are nested namespaces and using
statements on all levels.
We can not use the root node Usings
property or a single LINQ statement anymore to get all the using statements in the code.
The goal for this exercise is to get a list of all using statements that are not part of System
.
To interact with the syntax tree, cast the root of the tree into a CompilationUnitSyntax
class.
var root = (CompilationUnitSyntax)tree.GetRoot();
There are two types of syntax visitors you can use to walk to through a syntax tree. The CSharpSyntaxWalker
and the CSharpSyntaxRewriter
.
Rewriters are useful if you want to change the tree, like analyzers are doing.
For documentation we only want to read the tree, so the Syntax Walker is more appropriate.
Create a new file called UsingCollector.cs
.
On top of the file, add the following namespaces.
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Make the class inherit from CSharpSyntaxWalker
.
class UsingCollector : CSharpSyntaxWalker
{
}
The base class has about 240 virtual methods you can override for your specific functionality.
In this example you are only interested in declarations of using statements. For this you override the VisitUsingDirective
method.
public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
}
Whenever the visitor visits a Using Directive Syntax node anywhere in the tree, this method will be invoked.
- Check if each namespace is not part of
System
. - Store the list of discovered Using Directives in a property called
Usings
on theUsingCollector
class.
If you are not able to come up with the code yourself, you can use the following code:
class UsingCollector : CSharpSyntaxWalker
{
public readonly List<UsingDirectiveSyntax> Usings = new List<UsingDirectiveSyntax>();
public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
if (node.Name.ToString() != "System" &&
!node.Name.ToString().StartsWith("System.", StringComparison.Ordinal))
{
this.Usings.Add(node);
}
}
}
With the Syntax Walker in place, go back to the Main
method and create an instance of the walker and execute the Visit
method using the root as argument.
var collector = new UsingCollector();
collector.Visit(root);
- List all Usings that are not part of
System
.
If you are not able to come up with the code yourself, you can use the following code:
foreach (var directive in collector.Usings)
{
Console.WriteLine(directive.Name);
}
You can compare your project with the RoslynWalkTrees solution.
The output should read:
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp