Create Syntax Trees using Roslyn APIs

01/07/2025
6 minute read

Why?

In the previous post we talked about Roslyn and how it helps us to have extended interaction with the C# Compiler, that being said, there are situations that we need to generate some code as source generators or when we are creating a code analyzer and want to have a code fixer for that, we need to create or change a syntax tree. The syntax tree is the result of the first phase, Parser, in the pipeline.

The image below is the syntax tree representing the following code, I have used SharpLab.io to get it, it is a tool that every C# developer should be familiar with, it could show the Compiler generated C# code, IL code, or the Syntax Tree representing the code.

using System;

Console.WriteLine("🌄");

The syntax tree contains of various type of nodes, the blue ones in the image are SyntaxNode, they are abstract classes that could contain other types of nodes, the yellow ones (if you have visual studio and using Syntax Tree Visualizer, they are green) are SyntaxToken, they are structs and are the termination in the tree, even though SyntaxTrivia struct type are attached to them, the trivias do not contribute to the semantic of the program, but to the formatting and layout of it. Syntax tokens are things like identifiers, keywords, etc. Well, now to create a syntax tree there are two approaches (at least that I am aware of it 😉) one could leverage.

Syntax Tree - Lets create one

The first and easier approach is to use a string and the the methods from factory methods, SyntaxFactory.ParseSyntaxTree, and CSharpSyntaxTree.ParseText:

const string sourceCode = """
                          using System;

                          namespace BuildingCode;

                          public class Program
                          {
                            static void Main(string[] args)
                            {
                                Console.WriteLine("Hello Sir Ta Piaz!");
                            }
                          }
                          """;

// 1. Create a Syntax Tree
var syntaxTree1 = SyntaxFactory.ParseSyntaxTree(sourceCode);
var syntaxTree2 = CSharpSyntaxTree.ParseText(sourceCode);

The second way to create a syntax tree is using other factory methods provided by the SyntaxFactory class, since the syntax tree is Immutable, every call returns a new version of the previous one and not changing it. Writing all of this code by hand might be tedious and cumbersome, therefore, I used another tool that every C# developer working with Roslyn should be aware of: Roslyn Quoter

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

var compilationUnitSyntaxNode = 
CompilationUnit()
.WithUsings(
    SingletonList<UsingDirectiveSyntax>(
        UsingDirective(
            IdentifierName("System"))))
.WithMembers(
    SingletonList<MemberDeclarationSyntax>(
        GlobalStatement(
            ExpressionStatement(
                InvocationExpression(
                    MemberAccessExpression(
                        SyntaxKind.SimpleMemberAccessExpression,
                        IdentifierName("Console"),
                        IdentifierName("WriteLine")))
                .WithArgumentList(
                    ArgumentList(
                        SingletonSeparatedList<ArgumentSyntax>(
                            Argument(
                                LiteralExpression(
                                    SyntaxKind.StringLiteralExpression,
                                    Literal("🌄"))))))))))
.NormalizeWhitespace()

That's it! There are two more things I would like to mention in here, 1. the root node of the tree is CompilationUnit, it is kind of representing a C# file, and contains high level nodes like using directives, namespaces, global statements, type declarations, etc. Having a CompilationUnitSyntax at the root gives us a consistent structure for the API knowing where to look if you want to investigate top-level code components, also, a single entry point guarantees we can traverse all constructs in the file without missing anything! 2. the last line of code is a call to NormalizeWhitespace, this just applies the common formatting to the code by adding some trivia where necessary.

Conclusion

There are a couple of ways to generate a syntax tree in C#, using SyntaxFactory.ParseSyntaxTree and CSharpSyntaxTree.ParseText, or using other factory methods provided by the SyntaxFactory class, choose your weapon as required 😁 Remember, a tree has knowledge about the textual content of the tree's information and how that content relates to C#, with CompilationUnit being the root of the tree representing a C# file. This design allows developers to reliably write analyzers, refactoring, and source generators that operate on a guaranteed structure, simplifying the process of parsing, inspecting, and modifying C# code. Do not forget, that everything in Roslyn is Immutable, be careful with assigning your variables because it is easy to forget about this aspect.

Thanks for reading so far, enjoy coding and Dametoon Garm [1] 😊

Resources

Buy Me a Coffee at ko-fi.com

Test Your Roslyn Code Analyzers

In the previous post we have seen how it is possible to create a diagnostic analyzer using Roslyn APIs. Being a TDD advocate it would be disappointing not to talk about how we could test our code analyzer! So let's take a look into it.

Write your own Code Analyzer with Roslyn

We are all familiar with diagnostics that are provided from the compiler when we develop an application, they could be in form of warnings, errors or code suggestions. A diagnostic or code analyzer, inspects our open files for various metrics, like style, maintainability, design, etc. However, sometimes we need to write a tailor-made analysis for our specific situation, tool, or project.

Changing Syntax Tree uing Roslyn API

A syntax tree has knowledge about the textual content of what it represents and that content relates to C#. There are scenarios that we need to change the code, such as code fixes, refactorings, or advanced source generation, and it could be done through changing the syntax tree; since this is a bidirectional relationship. There are two approaches, Replacement Methods and SyntaxRewriter classes

An error has occurred. This application may no longer respond until reloaded. Reload x