Translate

jueves, 4 de mayo de 2023

Edición de documentos con DocumentEditor

Una desventaja de la inmutabilidad de Roslyn es que a veces puede resultar complicado aplicar varios cambios a un documento o árbol de sintaxis. La inmutabilidad significa que cada vez que aplicamos cambios a un árbol de sintaxis, se nos proporciona un árbol de sintaxis completamente nuevo. De forma predeterminada, no podemos comparar nodos entre árboles, entonces, ¿qué hacemos cuando queremos realizar varios cambios en un árbol de sintaxis?

Roslyn nos da cuatro opciones:

  • Use CSharpSyntaxRewriter
  • Usar anotaciones 
  • Usar TrackNodes()
  • Usar el DocumentEditor

El Editor de documentos nos permite realizar múltiples cambios en un documento y obtener el documento resultante después de que se hayan aplicado los cambios. El DocumentEditor es una clase que hereda de SyntaxEditor.

Usaremos el DocumentEditor para cambiar:


char key = Console.ReadKey();

if(key == 'A')

{

    Console.WriteLine("You pressed A");

}

else

{

    Console.WriteLine("You didn't press A");

}


a:


char key = Console.ReadKey();

if(key == 'A')

{

    LogConditionWasTrue();

    Console.WriteLine("You pressed A");

}

else

{

    Console.WriteLine("You didn't press A");

    LogConditionWasFalse();

}


Usaremos DocumentEditor para insertar simultáneamente una invocación antes de la primera Console.WriteLine() y para insertar otra después de la segunda.

 Por lo general, obtendrá un documento de un espacio de trabajo :


var mscorlib = MetadataReference.CreateFromAssembly(typeof(object).Assembly);

var workspace = new AdhocWorkspace();

var projectId = ProjectId.CreateNewId();

var versionStamp = VersionStamp.Create();

var projectInfo = ProjectInfo.Create(projectId, versionStamp, "NewProject", "projName", LanguageNames.CSharp);

var newProject = workspace.AddProject(projectInfo);

var sourceText = SourceText.From(@"

class C

{

    void M()

    {

        char key = Console.ReadKey();

        if (key == 'A')

        {

            Console.WriteLine(""You pressed A"");

        }

        else

        {

            Console.WriteLine(""You didn't press A"");

        }

    }

}");

var document = workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText);

var syntaxRoot = await document.GetSyntaxRootAsync();

var ifStatement = syntaxRoot.DescendantNodes().OfType<IfStatementSyntax>().Single();


var conditionWasTrueInvocation =

SyntaxFactory.ExpressionStatement(

    SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName("LogConditionWasTrue"))

    .WithArgumentList(

                    SyntaxFactory.ArgumentList()

                    .WithOpenParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.OpenParenToken))

                    .WithCloseParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.CloseParenToken))))

            .WithSemicolonToken(

                SyntaxFactory.Token(

                    SyntaxKind.SemicolonToken));


var conditionWasFalseInvocation =

SyntaxFactory.ExpressionStatement(

    SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName("LogConditionWasFalse"))

    .WithArgumentList(

                    SyntaxFactory.ArgumentList()

                    .WithOpenParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.OpenParenToken))

                    .WithCloseParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.CloseParenToken))))

            .WithSemicolonToken(

                SyntaxFactory.Token(

                    SyntaxKind.SemicolonToken));


//Finally… create the document editor

var documentEditor = await DocumentEditor.CreateAsync(document);

//Insert LogConditionWasTrue() before the Console.WriteLine()

documentEditor.InsertBefore(ifStatement.Statement.ChildNodes().Single(), conditionWasTrueInvocation);

//Insert LogConditionWasFalse() after the Console.WriteLine()

documentEditor.InsertAfter(ifStatement.Else.Statement.ChildNodes().Single(), conditionWasFalseInvocation);


var newDocument = documentEditor.GetChangedDocument();


Todos los métodos familiares de SyntaxNode están aquí. Podemos Insertar, Reemplazar y Eliminar nodos como mejor nos parezca, todo basado en nodos en nuestro árbol de sintaxis original. Mucha gente encuentra este enfoque más intuitivo que construir un CSharpSyntaxRewriter completo.

¿Cómo podemos saber qué tipos de nodos son compatibles entre sí? No creo que haya una buena respuesta aquí. Esencialmente, tenemos que aprender qué nodos son compatibles nosotros mismos. Como de costumbre, Syntax Visualizer y Roslyn Quoter son las mejores herramientas para determinar qué tipo de nodos debe crear.

Vale la pena señalar que DocumentEditor expone el SemanticModel de su documento original. Es posible que necesite esto cuando edite el documento original y tome decisiones sobre lo que le gustaría cambiar.

También vale la pena señalar que el SyntaxEditor subyacente expone un SyntaxGenerator que puede usar para crear nodos de sintaxis sin depender de SyntaxFactory, que es más detallado.