Search code examples
c#.netroslyninsertion

C# Roslyn API: Insert Instructions/Methods between each nodes members


I just got into a personal project and I would like to be able to use the Roslyn API to insert instructions / methods between class members (So methods / instructions)

I am currently able to retrieve the different child nodes like here: Namespace> Class> Fields / Methods

But I would like to know how I can insert code between fields / methods and not replace code.

(At the location of the red lines below for example)

Nodes

EDIT: After some more research I found that I can use InsertTokensBefore, InsertNodesBefore or InsertTriviaBefore but I don't understand how to parse my functions (actually in text/string format) to the needed parameters.


Solution

  • You may use the Microsoft.CodeAnalysis.CSharp.SyntaxFactory to build a Microsoft.CodeAnalysis.SyntaxNode.

    To get the syntax factory code that you need in order to create new syntax trees, have a look at the fantastic RoslynQuoter. There, you can put in the C# program that you want to build via the syntax factory API:

    public class RoslynClass
    {
        public static int RoslynMethod(int left, int right)
        {
            return left + right;
        }
    }
    

    Check all options to your preference, and Get Roslyn API calls to generate this code!, which you can (partially) use in your production code.

    Here I list an example of a source code refactoring, which inserts the method public static int RoslynMethod(int left, int right) before/after either a field or a method:

    using System.Composition;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CodeActions;
    using Microsoft.CodeAnalysis.CodeRefactorings;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    using Microsoft.CodeAnalysis.Editing;
    
    namespace RoslynTool
    {
        [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(MethodInserter))]
        [Shared]
        internal sealed class MethodInserter : CodeRefactoringProvider
        {
            public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
            {
                SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
                SyntaxNode node = root.FindNode(context.Span);
    
                if (node is FieldDeclarationSyntax or MethodDeclarationSyntax)
                {
                    SyntaxNode newNode = CreateMethodNode();
    
                    context.RegisterRefactoring(CodeAction.Create("Insert method before", ct => InsertBeforeAsync(context.Document, node, newNode, ct)));
                    context.RegisterRefactoring(CodeAction.Create("Insert method after", ct => InsertAfterAsync(context.Document, node, newNode, ct)));
                }
            }
    
            private static MemberDeclarationSyntax CreateMethodNode()
            {
                return SyntaxFactory.MethodDeclaration(
                    SyntaxFactory.PredefinedType(
                        SyntaxFactory.Token(SyntaxKind.IntKeyword)),
                    SyntaxFactory.Identifier("RoslynMethod"))
                    .WithModifiers(
                        SyntaxFactory.TokenList(new[] {
                            SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                            SyntaxFactory.Token(SyntaxKind.StaticKeyword)}))
                    .WithParameterList(
                        SyntaxFactory.ParameterList(
                            SyntaxFactory.SeparatedList<ParameterSyntax>(new SyntaxNodeOrToken[] {
                                SyntaxFactory.Parameter(
                                    SyntaxFactory.Identifier("left"))
                                .WithType(
                                    SyntaxFactory.PredefinedType(
                                        SyntaxFactory.Token(SyntaxKind.IntKeyword))),
                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.Parameter(
                                    SyntaxFactory.Identifier("right"))
                                .WithType(
                                    SyntaxFactory.PredefinedType(
                                        SyntaxFactory.Token(SyntaxKind.IntKeyword)))})))
                    .WithBody(
                        SyntaxFactory.Block(
                            SyntaxFactory.SingletonList<StatementSyntax>(
                                SyntaxFactory.ReturnStatement(
                                    SyntaxFactory.BinaryExpression(
                                        SyntaxKind.AddExpression,
                                        SyntaxFactory.IdentifierName("left"),
                                        SyntaxFactory.IdentifierName("right"))))));
            }
    
            private static async Task<Document> InsertBeforeAsync(Document document, SyntaxNode location, SyntaxNode newNode, CancellationToken cancellationToken)
            {
                DocumentEditor documentEditor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
                documentEditor.InsertBefore(location, newNode);
    
                return documentEditor.GetChangedDocument();
            }
    
            private static async Task<Document> InsertAfterAsync(Document document, SyntaxNode location, SyntaxNode newNode, CancellationToken cancellationToken)
            {
                DocumentEditor documentEditor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
                documentEditor.InsertAfter(location, newNode);
    
                return documentEditor.GetChangedDocument();
            }
        }
    }
    

    insert method via roslyn source code refactorings in Visual Studio 2019

    Alternatively, the parse methods of the SyntaxFactory could be used to create a new SyntaxNode, but I had some trouble getting whitespaces/trivia right in that example:

    namespace RoslynTool
    {
        internal sealed class MethodInserter : CodeRefactoringProvider
        {
            private static MemberDeclarationSyntax CreateMethodNode()
            {
                return SyntaxFactory.ParseMemberDeclaration(@"
            public static int RoslynMethod(int left, int right)
            {
                return left + right;
            }
    ");
            }
        }
    }
    

    I hope I understood your question correctly.