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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} |
The output of this program produces a simple program without any redundant semicolons.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Sample | |
{ | |
public void Foo() | |
{ | |
Console.WriteLine(); | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} |
The output of this approach is:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Sample | |
{ | |
public void Foo() | |
{ | |
Console.WriteLine(); | |
#region SomeRegion | |
//Some other code | |
#endregion | |
} | |
} |
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.
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?
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).
Got it working ..Thanks !!!