Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building a SyntaxTree from the ground up

Tags:

c#

roslyn

I previously asked this question, which was answered, but someone gave a suggestion that might help me prevent making similar mistakes as I move forward.

Adding Auto-Implemented Property to class using Roslyn

The suggestion was that I build the Syntax Tree from the bottom up and not from the top down. Could someone provide a small demo or a link that shows how I would do this from the ground up?

Here is the code again:

var root = (CompilationUnitSyntax)document.GetSyntaxRoot();

    // Add the namespace
    var namespaceAnnotation = new SyntaxAnnotation();
    root = root.WithMembers(
        Syntax.NamespaceDeclaration(
            Syntax.ParseName("ACO"))
                .NormalizeWhitespace()
                .WithAdditionalAnnotations(namespaceAnnotation));
    document = document.UpdateSyntaxRoot(root);

    // Add a class to the newly created namespace, and update the document
    var namespaceNode = (NamespaceDeclarationSyntax)root
        .GetAnnotatedNodesAndTokens(namespaceAnnotation)
        .Single()
        .AsNode();

    var classAnnotation = new SyntaxAnnotation();
    var baseTypeName = Syntax.ParseTypeName("System.Windows.Forms.Form");
    SyntaxTokenList syntaxTokenList = new SyntaxTokenList()
        {
            Syntax.Token(SyntaxKind.PublicKeyword)
        };

    var newNamespaceNode = namespaceNode
        .WithMembers(
            Syntax.List<MemberDeclarationSyntax>(
                Syntax.ClassDeclaration("MainForm")
                    .WithAdditionalAnnotations(classAnnotation)
                    .AddBaseListTypes(baseTypeName)
                    .WithModifiers(Syntax.Token(SyntaxKind.PublicKeyword))));

    root = root.ReplaceNode(namespaceNode, newNamespaceNode).NormalizeWhitespace();
    document = document.UpdateSyntaxRoot(root);


    var attributes = Syntax.List(Syntax.AttributeDeclaration(Syntax.SeparatedList(Syntax.Attribute(Syntax.ParseName("STAThread")))));


    // Find the class just created, add a method to it and update the document
    var classNode = (ClassDeclarationSyntax)root
        .GetAnnotatedNodesAndTokens(classAnnotation)
        .Single()
        .AsNode();

        var syntaxList = Syntax.List<MemberDeclarationSyntax>(
                Syntax.MethodDeclaration(
                    Syntax.ParseTypeName("void"), "Main")
                    .WithModifiers(Syntax.TokenList(Syntax.Token(SyntaxKind.PublicKeyword)))
                    .WithAttributes(attributes)
                    .WithBody(
                        Syntax.Block()));
        syntaxList = syntaxList.Add(Syntax.PropertyDeclaration(Syntax.ParseTypeName("System.Windows.Forms.Timer"), "Ticker"));
        var newClassNode = classNode
            .WithMembers(syntaxList);

    root = root.ReplaceNode(classNode, newClassNode).NormalizeWhitespace();
    document = document.UpdateSyntaxRoot(root);

So how would I do the same thing, but from the ground up?

Thanks in advance,

Bob

P.S. My property is also missing the "get; set;" text within it. Could someone comment on what I am forgetting to add which causes this text to be added to the property?

like image 727
Beaker Avatar asked Jul 05 '12 20:07

Beaker


Video Answer


2 Answers

Believe it or not, I have written a tool called Roslyn Code Quoter especially to answer this question.

http://roslynquoter.azurewebsites.net

The tool can take any C# program and automatically generate a snippet of code like Matt has written above. Since it also generates everything perfectly including all the whitespace, the code can get rather unwieldy. But you can exclude parts that generate trivia, and then just call NormalizeWhitespace() on the resulting node, it will automatically insert the trivia so that the code is correctly formatted.

For the sake of completeness I'm posting the code in all its gory detail, so that you can see how to construct whitespace and all those little details.

CompilationUnit().WithMembers(
SingletonList<MemberDeclarationSyntax>(
    NamespaceDeclaration(
        IdentifierName("ACO"))
    .WithMembers(
        SingletonList<MemberDeclarationSyntax>(
            ClassDeclaration("MainForm")
            .WithModifiers(
                TokenList(
                    Token(SyntaxKind.PublicKeyword)))
            .WithBaseList(
                BaseList(
                    SingletonSeparatedList<BaseTypeSyntax>(
                        SimpleBaseType(
                            QualifiedName(
                                QualifiedName(
                                    QualifiedName(
                                        IdentifierName("System"),
                                        IdentifierName("Windows")),
                                    IdentifierName("Forms")),
                                IdentifierName("Form"))))))
            .WithMembers(
                List<MemberDeclarationSyntax>(
                    new MemberDeclarationSyntax[]{
                        PropertyDeclaration(
                            QualifiedName(
                                QualifiedName(
                                    QualifiedName(
                                        IdentifierName("System"),
                                        IdentifierName("Windows")),
                                    IdentifierName("Forms")),
                                IdentifierName("Timer")),
                            Identifier("Ticker"))
                        .WithModifiers(
                            TokenList(
                                Token(SyntaxKind.PublicKeyword)))
                        .WithAccessorList(
                            AccessorList(
                                List<AccessorDeclarationSyntax>(
                                    new AccessorDeclarationSyntax[]{
                                        AccessorDeclaration(
                                            SyntaxKind.GetAccessorDeclaration)
                                        .WithSemicolonToken(
                                            Token(SyntaxKind.SemicolonToken)),
                                        AccessorDeclaration(
                                            SyntaxKind.SetAccessorDeclaration)
                                        .WithSemicolonToken(
                                            Token(SyntaxKind.SemicolonToken))}))),
                        MethodDeclaration(
                            PredefinedType(
                                Token(SyntaxKind.VoidKeyword)),
                            Identifier("Main"))
                        .WithAttributeLists(
                            SingletonList<AttributeListSyntax>(
                                AttributeList(
                                    SingletonSeparatedList<AttributeSyntax>(
                                        Attribute(
                                            IdentifierName("STAThread"))))))
                        .WithModifiers(
                            TokenList(
                                Token(SyntaxKind.PublicKeyword)))
                        .WithBody(
                            Block())}))))))
.NormalizeWhitespace()
like image 126
Kirill Osenkov Avatar answered Sep 30 '22 12:09

Kirill Osenkov


This will build up your entire compilation unit tree in one expression.

var cu = SyntaxFactory.CompilationUnit()
            .AddMembers(
                SyntaxFactory.NamespaceDeclaration(Syntax.IdentifierName("ACO"))
                        .AddMembers(
                        SyntaxFactory.ClassDeclaration("MainForm")
                            .AddBaseListTypes(SyntaxFactory.ParseTypeName("System.Windows.Forms.Form"))
                            .WithModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                            .AddMembers(
                                Syntax.PropertyDeclaration(SyntaxFactory.ParseTypeName("System.Windows.Forms.Timer"), "Ticker")
                                        .AddAccessorListAccessors(
                                        SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                                        SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))),
                                SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("void"), "Main")
                                        .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                                        .AddAttributes(SyntaxFactory.AttributeDeclaration().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("STAThread"))))
                                        .WithBody(SyntaxFactory.Block())
                                )
                        )
                );

Of course, you don't have to do it as a single expression. I could have used separate local variables to collect the pieces I wanted and then added them in the construction of the containing syntax piece.

like image 44
Matt Warren Avatar answered Sep 30 '22 13:09

Matt Warren