Translate

jueves, 20 de abril de 2023

CSharpSyntaxRewriter

En el post anterior hablamos de CSharpSyntaxWalker y cómo podríamos navegar el árbol de sintaxis con el patrón de visitor. Ahora vamos un paso más allá con CSharpSyntaxRewriter y "modificamos" el árbol sintactico a medida que lo recorremos. Es importante tener en cuenta que en realidad no estamos mutando el árbol sintactico original, ya que los árboles de Roslyn son inmutables. En su lugar, CSharpSyntaxRewriter crea un nuevo árbol como resultado de nuestros cambios.

CSharpSyntaxRewriter puede visitar todos los nodos, tokens o trivias dentro de un árbol sintactico. Al igual que CSharpSyntaxVisitor, podemos elegir de forma selectiva qué fragmentos de sintaxis nos gustaría visitar. Hacemos esto sobreescribiendo varios métodos y devolviendo lo siguiente:

  • El nodo, token o trivia original, sin cambios.
  • Nulo, que indica que se debe eliminar el nodo, el token o la trivia.
  • Un nuevo nodo de sintaxis, token o trivia.

Al igual que con la mayoría de las API, CSharpSyntaxRewriter se comprende mejor a través de ejemplos. Una pregunta reciente sobre Stack Overflow preguntó ¿Cómo puedo eliminar los puntos y comas redundantes en el código con SyntaxRewriter?

Roslyn trata todos los puntos y coma redundantes como parte de un nodo EmptyStatementSyntax. A continuación, demostramos cómo resolver el caso base: un punto y coma innecesario en una línea propia.


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());

}


La salida de este programa produce un programa simple sin ningún punto y coma redundante.


public class Sample

{

   public void Foo()

   {

      Console.WriteLine();

    }

}

Sin embargo, cuando hay trivia inicial o final, esta trivia se elimina. Esto significa que se eliminarán los comentarios por encima y por debajo del punto y coma. Al construir un EmptyStatementSyntax con un token faltante en lugar de un punto y coma, podemos eliminar el punto y coma del árbol original:

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());

}


El resultado de este enfoque es:


public class Sample

{

   public void Foo()

   {

      Console.WriteLine();

      #region SomeRegion

      //Some other code

      #endregion

    }

}

Este enfoque tiene el efecto secundario de dejar una línea en blanco donde haya un punto y coma redundante. Dicho esto, creo que probablemente valga la pena el intercambio, ya que de lo contrario no parece haber una forma de retener las trivias. En última instancia, la trivia solo se puede conservar si se adjunta a un nodo y luego se devuelve ese nodo.

La única forma de eliminar el nodo y conservar las trivias es construir un nodo de reemplazo. El mejor candidato para el reemplazo probablemente sea un EmptyStatementSyntax al que le falte un punto y coma.

Esto también podría indicar una limitación con CSharpSyntaxRewriter. Parece que debería ser más fácil eliminar nodos, manteniendo sus trivias.