Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep structured trivia when removing a node

Is there an easy way to remove a SyntaxNode (i.e. a method) from the tree, but keeping the structured trivia?

In the following code I want to remove MethodA:

public class Sample 
{ 
  #region SomeRegion 
  public void MethodA() 
  { 

  }
  #endregion 
} 

I use a CSharpSyntaxRewriter to rewrite the SyntaxTree. In the VisitMethodDeclaration method, I simply return null for MethodA. The problem with this approach is that the StructuredTrivia of the #region tag is also removed. This is the result result:

public class Sample 
{ 
  #endregion 
}

In my CSharpSyntaxRewriter:

public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) 
{ 
   if (...) 
      return null; 
   else 
      return node; 
} 

EDIT: As mentioned in one of the answers below, I could use SyntaxNode.RemoveNodes with the SyntaxRemoveOptions.KeepDirectives option. This solution has two big downsides:

  1. I need to know which SyntaxNode types can contain this child type. For example a method can be declared in a Struct, Class, Interface, etc ... Which means I need to do the filtering on multiple locations.
  2. I loose the ability to build the Syntax Tree from the bottom up. This gives problems when comparing SyntaxTree objects. All subsequent calls to the visitor methods already see the new nodes that are created in the "RemoveNodes" method. With the SyntaxNode.RemoveNodes method it's possible to specify SyntaxRemoveOptions.KeepDirectives, but is this also possible with CSharpSyntaxRewriter?

EDIT2: Here is some code with what I'm trying to do: https://dotnetfiddle.net/1Cg6UZ

like image 777
TWT Avatar asked Aug 05 '16 09:08

TWT


1 Answers

When deleting the node you're actually removing the trivia with it. To retain the trivia you need to modify the ClassDeclarationSyntax instead of the MethodDeclaration.

When visiting the ClassDeclarationSyntax you can modify the class by removing the appropriate nodes - and by using the SyntaxRemoveOptions.KeepTrailingTrivia | SyntaxRemoveOptions.KeepLeadingTrivia you retain the comments and region statements before and after the actual method definition.

public class ClassDeclarationChanger : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        var methods = node.Members.OfType<MethodDeclarationSyntax>();
        if (methods.Any())
        {
            node = node.RemoveNodes(methods, SyntaxRemoveOptions.KeepTrailingTrivia | 
                    SyntaxRemoveOptions.KeepLeadingTrivia);
        }
        return base.VisitClassDeclaration(node);
    }
}

In case you wish to visit the child nodes first you can of course also execute base.VisitClassDeclaration(node) first and remove the method node only afterwars.

A different approach would be to return another declaration. However you cannot simply return an EmptyStatement (as this will result in an exception) but you can insert a new method declaration with no content:

public class SampleChanger : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        // Generates a node containing only parenthesis 
        // with no identifier, no return type and no parameters
        var newNode = SyntaxFactory.MethodDeclaration(SyntaxFactory.IdentifierName(""), "");
        // Removes the parenthesis from the Parameter List 
        // and replaces them with MissingTokens
        newNode = newNode.ReplaceNode(newNode.ParameterList,
            newNode.ParameterList.WithOpenParenToken(
                SyntaxFactory.MissingToken(SyntaxKind.OpenParenToken)).
            WithCloseParenToken(SyntaxFactory.MissingToken(SyntaxKind.CloseParenToken)));
        // Returns the new method containing no content 
        // but the Leading and Trailing trivia of the previous node
        return newNode.WithLeadingTrivia(node.GetLeadingTrivia()).
            WithTrailingTrivia(node.GetTrailingTrivia());
    }
}

This approach of course does have the downside that it requires a specific SyntaxNode for different syntax types so reusing it for other parts of your code might be difficult.

like image 167
SJP Avatar answered Nov 14 '22 00:11

SJP