roslynCambiar el código fuente con Roslyn

Introducción

Ejemplos prácticos de usar Roslyn para transformaciones de código fuente.

Observaciones

  • Los árboles de sintaxis de Roslyn son inmutables. Al llamar a un método como ReplaceNodes, generamos un nuevo nodo en lugar de modificar el existente. Esto requiere que siempre cambies el objeto en el que has estado trabajando.

Reemplace los atributos existentes para todos los métodos en C # usando el árbol de sintaxis

El siguiente fragmento de código reemplaza todos los Atributos llamados PreviousAttribute por un Atributo llamado ReplacementAttribute para una solución completa. La muestra busca manualmente el árbol de Sintaxis y reemplaza todos los nodos afectados.

    static async Task<bool> ModifySolution(string solutionPath)
    {
        using (var workspace = MSBuildWorkspace.Create())
        {
            // Selects a Solution File
            var solution = await workspace.OpenSolutionAsync(solutionPath);
            // Iterates through every project
            foreach (var project in solution.Projects)
            {
                // Iterates through every file
                foreach (var document in project.Documents)
                {
                    // Selects the syntax tree
                    var syntaxTree = await document.GetSyntaxTreeAsync();
                    var root = syntaxTree.GetRoot();
                    // Finds all Attribute Declarations in the Document
                    var existingAttributesList = root.DescendantNodes().OfType<AttributeListSyntax>()
                        // Where the Attribute is declared on a method
                        .Where(curr => curr.Parent is MethodDeclarationSyntax)
                        // And the attribute is named "PreviousAttribute"
                        .Where(curr => curr.Attributes.Any(currentAttribute => currentAttribute.Name.GetText().ToString() == "PreviousAttribute"))
                        .ToList();
                    if (existingAttributesList.Any())
                    {
                        // Generates a replacement for every attribute
                        var replacementAttribute = SyntaxFactory.AttributeList(
                            SyntaxFactory.SingletonSeparatedList(
                            SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("ReplacementAttribute"),
                                SyntaxFactory.AttributeArgumentList(
                                    SyntaxFactory.SeparatedList(new[]
                                    {
                                    SyntaxFactory.AttributeArgument(
                                        SyntaxFactory.LiteralExpression(
                                            SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(@"Sample"))
                                        )
                                    })))));
                        // Replaces all attributes at once.
                        // Note that you should not use root.ReplaceNode
                        // since it would only replace the first note
                        root = root.ReplaceNodes(existingAttributesList, (node, n2) => replacementAttribute);
                        // Exchanges the document in the solution by the newly generated document
                        solution = solution.WithDocumentSyntaxRoot(document.Id, root);
                    }
                }
            }
            // applies the changes to the solution
            var result = workspace.TryApplyChanges(solution);
            return result;
        }
    }

El ejemplo anterior se puede probar para la siguiente clase:

public class Program
{
    [PreviousAttribute()]
    static void Main(string[] args)
    {
    }
}

No debe utilizar Methode root.ReplaceNode para reemplazar varios nodos. Como el árbol es inmutable, estarás trabajando en diferentes objetos. El uso del siguiente fragmento de código en el ejemplo anterior no generaría el resultado esperado:

foreach(var node in existingAttributesList){
    root = root.ReplaceNode(node, replacementAttribute);
}

La primera llamada a ReplaceNode crearía un nuevo elemento raíz. Sin embargo, los elementos en existingAttributesList pertenecen a una raíz diferente (el elemento raíz anterior) y no pueden reemplazarse debido a esto. Esto daría como resultado que el primer atributo se reemplace y los siguientes atributos permanezcan sin cambios, ya que todas las llamadas consecutivas se realizarán en un nodo que no esté presente en el nuevo árbol.

Reemplace los atributos existentes para todos los métodos en C # usando un SyntaxRewriter

El siguiente fragmento de código reemplaza todos los Atributos llamados "Atributo Anterior" por un Atributo llamado "Atributo de Reemplazo" para una solución completa. La muestra utiliza manualmente un SyntaxRewriter para intercambiar los atributos.

/// <summary>
/// The CSharpSyntaxRewriter allows to rewrite the Syntax of a node
/// </summary>
public class AttributeStatementChanger : CSharpSyntaxRewriter
{
    /// Visited for all AttributeListSyntax nodes
    /// The method replaces all PreviousAttribute attributes annotating a method by ReplacementAttribute attributes
    public override SyntaxNode VisitAttributeList(AttributeListSyntax node)
    {
        // If the parent is a MethodDeclaration (= the attribute annotes a method)
        if (node.Parent is MethodDeclarationSyntax &&
            // and if the attribute name is PreviousAttribute
            node.Attributes.Any(
                currentAttribute => currentAttribute.Name.GetText().ToString() == "PreviousAttribute"))
        {
            // Return an alternate node that is injected instead of the current node
            return SyntaxFactory.AttributeList(
                            SyntaxFactory.SingletonSeparatedList(
                            SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("ReplacementAttribute"),
                                SyntaxFactory.AttributeArgumentList(
                                    SyntaxFactory.SeparatedList(new[]
                                    {
                                    SyntaxFactory.AttributeArgument(
                                        SyntaxFactory.LiteralExpression(
                                            SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(@"Sample"))
                                        )
                                    })))));
        }
        // Otherwise the node is left untouched
        return base.VisitAttributeList(node);
    }
}

/// The method calling the Syntax Rewriter
private static async Task<bool> ModifySolutionUsingSyntaxRewriter(string solutionPath)
{
    using (var workspace = MSBuildWorkspace.Create())
    {
        // Selects a Solution File
        var solution = await workspace.OpenSolutionAsync(solutionPath);
        // Iterates through every project
        foreach (var project in solution.Projects)
        {
            // Iterates through every file
            foreach (var document in project.Documents)
            {
                // Selects the syntax tree
                var syntaxTree = await document.GetSyntaxTreeAsync();
                var root = syntaxTree.GetRoot();

                // Generates the syntax rewriter
                var rewriter = new AttributeStatementChanger();
                root = rewriter.Visit(root);

                // Exchanges the document in the solution by the newly generated document
                solution = solution.WithDocumentSyntaxRoot(document.Id, root);
            }
        }
        // applies the changes to the solution
        var result = workspace.TryApplyChanges(solution);
        return result;
    }
}

El ejemplo anterior se puede probar para la siguiente clase:

public class Program
{
    [PreviousAttribute()]
    static void Main(string[] args)
    {
    }
}