Learn Roslyn Now: Part 5 CSharpSyntaxRewriter

In Part 4, we discussed the abstract CSharpSyntaxWalker and how we could navigate the syntax tree with the visitor pattern. Today, we go one step further with the CSharpSyntaxRewriter, and “modify” the syntax tree as we traverse it. It’s important to note that we’re not actually mutating the original syntax tree, as Roslyn’s syntax trees are immutable. Instead, the CSharpSyntaxRewriter creates a new syntax tree resulting from our changes.

The CSharpSyntaxRewriter can visit all nodes, tokens or trivia within a syntax tree. Like the CSharpSyntaxVisitor, we can selectively choose what pieces of syntax we’d like to visit. We do this by overriding various methods and returning one of the following:

  • The original, unchanged node, token or trivia.
  • Null, signalling the node, token or trivia is to be removed.
  • A new syntax node, token or trivia.

As with most APIs, the CSharpSyntaxRewriter is best understood through examples. A recent question on Stack Overflow asked How can I removeĀ redundant semicolons in code with SyntaxRewriter?

Roslyn treats all redundant semicolons as part of an EmptyStatementSyntax node. Below, we demonstrate how to solve the base case: an unnecessary semicolon on a line of its own.


public class EmtpyStatementRemoval : CSharpSyntaxRewriter
{
public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node)
{
//Simply remove all Empty Statements
return null;
}
}
public static void Main(string[] args)
{
//A syntax tree with an unnecessary semicolon on its own line
var tree = CSharpSyntaxTree.ParseText(@"
public class Sample
{
public void Foo()
{
Console.WriteLine();
;
}
}");
var rewriter = new EmtpyStatementRemoval();
var result = rewriter.Visit(tree.GetRoot());
Console.WriteLine(result.ToFullString());
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

The output of this program produces a simple program without any redundant semicolons.


public class Sample
{
public void Foo()
{
Console.WriteLine();
}
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

However, odulkanberoglu points out some problems with this approach. When either leading or trailing trivia is present, this trivia is removed. This means, comments above and below the semicolon will be stripped out.

svick has a pretty clever workaround. By constructing an EmptyStatementSyntax with a missing token instead of a semicolon, we can manage to remove the semicolon from the original tree. His approach is demonstrated below:


public class EmtpyStatementRemoval : CSharpSyntaxRewriter
{
public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node)
{
//Construct an EmptyStatementSyntax with a missing semicolon
return node.WithSemicolonToken(
SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken)
.WithLeadingTrivia(node.SemicolonToken.LeadingTrivia)
.WithTrailingTrivia(node.SemicolonToken.TrailingTrivia));
}
}
public static void Main(string[] args)
{
var tree = CSharpSyntaxTree.ParseText(@"
public class Sample
{
public void Foo()
{
Console.WriteLine();
#region SomeRegion
//Some other code
#endregion
;
}
}");
var rewriter = new EmtpyStatementRemoval();
var result = rewriter.Visit(tree.GetRoot());
Console.WriteLine(result.ToFullString());
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

The output of this approach is:


public class Sample
{
public void Foo()
{
Console.WriteLine();
#region SomeRegion
//Some other code
#endregion
}
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

This approach has the side effect of leaving a blank line wherever there was a redundant semicolon. That being said, I think it’s probably worth the trade-off as there doesn’t seem to be a way to retain trivia otherwise. Ultimately, the trivia can only be retained by attaching it to a node, and then returning that node.

An aside: I suspect this will be the de facto approach to removing any syntax nodes in the future. It’s highly likely that any syntax node one might wish to remove might have associated comment trivia. The only way to remove the node while retaining the trivia is to construct a replacement node. The best candidate for replacement will likely be an EmptyStatementSyntax with a missing semicolon.

This might also indicate a limitation with the CSharpSyntaxRewriter. It seems like it should be easier to remove nodes, while retaining their trivia.

3 thoughts on “Learn Roslyn Now: Part 5 CSharpSyntaxRewriter

  1. Is it possible to override multiple VisitMethods in a single Rewriter?? Fox example : I want to rename a class and hence the constructor inside it . So can i override VisitClassDeclaration and VisitConstructorDeclaration methods in the same rewriter and call base Visit to get the things done ? I tried doing it but could not get it working . Always the first method that is overridden gets called . How do you suggest this scenario should be handled?

    1. Yes you should definitely be able to override as many as you like. In one of my projects we’re overriding 20-30 of these methods. You have to make sure to call base.VisitClassDeclaration(node). (Or whatever the appropriate base method is).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s