Search code examples
roslyncode-analysisvisual-studio-extensions

How to insert a function into a class in VB.NET using Roslyn


I have some logic to add a function to class using Roslyn, which works with a C# project, but not with a VB project. I am using the DocumentEditor editor class (Microsoft.CodeAnalysis.Editing.DocumentEditor) to perform the update.

I start by finding the SyntaxNode corresponding to the class definition.

In C# this is a ClassDeclarationSyntax element.

Screenshot of Syntax Visualizer for VB class

In VB this is a ClassBlockSyntax element.

Screenshot of Syntax Visualizer for VB class

I generate the complete text of the new function in a string variable, and then create a SyntaxNode from the text.

For C# I use the method CSharpSyntaxTree.ParseText, approximately as follows:

var Tree = CSharpSyntaxTree.ParseText ( Code, CSharpParseOptions.Default ) ;
var Root = await Tree.GetRootAsync() as CompilationUnitSyntax ;
var Expr = Root.Members.FirstOrDefault()
                       .WithAdditionalAnnotations ( Formatter.Annotation ) ;  

Expr is then of type MethodDeclarationSyntax.

For VB I use the method VisualBasicSyntaxTree.ParseText, with almost identical code:

var Tree = VisualBasicSyntaxTree.ParseText ( Code ) ;
var Root = await Tree.GetRootAsync() as CompilationUnitSyntax ;
var Expr = Root.Members.FirstOrDefault() ;

In this case Expr is of type MethodBlockSyntax.

I then try to insert the new node into the class.

For C# I use

RoslynDocEditor.InsertAfter ( RoslynClass.ChildNodes.Last, Expr )    

where RoslynClass is the ClassBlockSyntax node, and a little later ...

RootNode  = RoslynDocEditor.GetChangedRoot()
RootNode  = Formatter.Format ( RootNode, Formatter.Annotation, VSWorkspace )
RoslynDoc = RoslynDoc.WithSyntaxRoot ( RootNode )
ApplyOK   = VSWorkspace.TryApplyChanges ( RoslynDoc.Project.Solution )

This adds the new function at the end of the class.

If I do the same for VB, it generates an InvalidOperationException at the line

RootNode  = RoslynDocEditor.GetChangedRoot()

with the description "The item specified is not the element of a list" and the stack trace:

at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitClassBlock(ClassBlockSyntax node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.ClassBlockSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.BaseListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitListElement[TNode](TNode node)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitList[TNode](SyntaxList`1 list)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.VisitList[TNode](SyntaxList`1 list)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitCompilationUnit(CompilationUnitSyntax node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.CompilationUnitSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.BaseListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.InsertNodeInList(SyntaxNode root, SyntaxNode nodeInList, IEnumerable`1 nodesToInsert, Boolean insertBefore)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxNode.InsertNodesInListCore(SyntaxNode nodeInList, IEnumerable`1 nodesToInsert, Boolean insertBefore)
at Microsoft.CodeAnalysis.SyntaxNodeExtensions.InsertNodesBefore[TRoot](TRoot root, SyntaxNode nodeInList, IEnumerable`1 newNodes)
at Microsoft.CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator.InsertDeclarationsBeforeInternal(SyntaxNode root, SyntaxNode declaration, IEnumerable`1 newDeclarations)
at Microsoft.CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator._Closure$__310-0._Lambda$__0(SyntaxNode r)
at Microsoft.CodeAnalysis.Editing.SyntaxGenerator.PreserveTrivia[TNode](TNode node, Func`2 nodeChanger)
at Microsoft.CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator.InsertNodesBefore(SyntaxNode root, SyntaxNode declaration, IEnumerable`1 newDeclarations)
at Microsoft.CodeAnalysis.Editing.SyntaxEditor.InsertChange.Apply(SyntaxNode root, SyntaxGenerator generator)
at Microsoft.CodeAnalysis.Editing.SyntaxEditor.GetChangedRoot()
at MultiLang.frmLanguageSwitching.VB$StateMachine_133_btAdd_Click.MoveNext() in C:\VSPackage_Version_7_1\Project\MultiLang\Forms\frmLanguageSwitching.vb:line 944

From the screen shot of the Syntax Visualizer you can see that the last child of the ClassBlock is the EndClassStatement, so it makes more sense to use

RoslynDocEditor.InsertBefore ( RoslynClass.ChildNodes.Last, NewFunctionNode )

but that generates exactly the same error as above.

Is there a way to insert a function in VB class in a similar manner, or does this only work in C#.


Solution

  • It works for VB, if I cast the ClassNode from a SyntaxNode (back) to ClassBlockSyntax and then use the Members collection

    var cbs = ClassNode as Microsoft.CodeAnalysis.VisualBasic.Syntax.ClassBlockSyntax ;
    RoslynDocEditor.InsertAfter ( cbs.Members.Last(), Expr ) ;
    

    The same works for C#, if I cast the ClassNode to ClassDeclarationSyntax

    var cds = ClassNode as Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax ;
    RoslynDocEditor.InsertAfter ( cds.Members.Last(), Expr ) ;
    

    but as already described, for C# it also works with

    RoslynDocEditor.InsertAfter ( RoslynClass.ChildNodes.Last, Expr ) ;
    

    So it looks like SyntaxNode.ChildNodes is equivalent to ClassDeclarationSyntax.Members for C#, but not equivalent to ClassBlockSyntax.Members for VB.


    EDIT

    It looks like an easier and a better solution is to use the AddMember extension method:

    RoslynDocEditor.AddMember ( RoslynClass, expr )