roslynCambia il codice sorgente con Roslyn

introduzione

Esempi pratici di utilizzo di Roslyn per le trasformazioni del codice sorgente.

Osservazioni

  • Gli alberi di sintassi di Roslyn sono immutabili. Chiamando un metodo come ReplaceNodes, generiamo un nuovo nodo anziché modificare quello esistente. Ciò richiede di cambiare sempre l'oggetto su cui hai lavorato.

Sostituisci gli attributi esistenti per tutti i metodi in C # usando la struttura della sintassi

Il seguente snippet sostituisce tutti gli attributi chiamati PreviousAttribute da un attributo denominato ReplacementAttribute per un'intera soluzione. L'esempio ricerca manualmente l'albero della sintassi e sostituisce tutti i nodi interessati.

    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;
        }
    }

L'esempio sopra può essere testato per la seguente classe:

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

Non si dovrebbe utilizzare il metodo root.ReplaceNode per sostituire più nodi. Poiché l'albero è immutabile, lavorerai su diversi oggetti. L'utilizzo del seguente frammento nell'esempio sopra non produrrebbe il risultato atteso:

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

La prima chiamata a ReplaceNode creerebbe un nuovo elemento radice. Tuttavia, gli elementi in existingAttributesList appartengono a una radice diversa (l'elemento radice precedente) e non possono essere sostituiti a causa di ciò. Ciò comporterebbe la sostituzione del primo Attributo e i seguenti Attributi rimasti invariati poiché tutte le chiamate consecutive verrebbero eseguite su un nodo non presente nella nuova struttura.

Sostituisci gli attributi esistenti per tutti i metodi in C # usando un SyntaxRewriter

Il seguente frammento sostituisce tutti gli Attributi denominati "PreviousAttribute" con un Attributo chiamato "ReplacementAttribute" per un'intera soluzione. L'esempio utilizza manualmente un SyntaxRewriter per scambiare gli attributi.

/// <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;
    }
}

L'esempio sopra può essere testato per la seguente classe:

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