Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace trivia with node in Roslyn

Tags:

c#

parsing

roslyn

For example, I have the following documentation comment in my C# code file:

/// add k+5

I want to REPLACE it with the node

_tst.AddElement(k+5);

How can I do it using C#/Roslyn? I've found how to add this line, but didn't found how to replace. My code that adds the node:

public static MethodDeclarationSyntax getChangedNode(MethodDeclarationSyntax method)
{
    var newmethod = method;
    var TestEntryArgName = "_tst";

    /* Adding _tst.AddElement(i); */
    foreach (var s in newmethod.Body.DescendantNodes())
    {
        SyntaxTrivia st = SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ");
        bool fl = false;
        bool before = true;
        var lt = s.GetLeadingTrivia();

        foreach (var triviaEntry in lt)
        {
            if (triviaEntry.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia)
            {
                fl = true;
                st = triviaEntry;
                break;
            }
        }

        if (!fl)
        {
            lt = s.GetTrailingTrivia();
            before = false;
            foreach (var triviaEntry in lt)
            {
                if (triviaEntry.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia)
                {
                    fl = true;
                    st = triviaEntry;
                    break;
                }
            }
            if (!fl) continue;
        }

        var commentContents = st.ToString();
        char[] delim = { ' ', '\n', '\t', '\r' };
        var ar = commentContents.Split(delim, StringSplitOptions.RemoveEmptyEntries);
        if (ar.Length != 2 || ar[0] != "add") continue;

        var lineToAdd = TestEntryArgName + ".AddElement(" + ar[1] + ")";
        var linelist = new List<ExpressionStatementSyntax>();
        linelist.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression(lineToAdd)));

        var childlist = s.Parent.ChildNodes();

        foreach (var si in childlist)
        {
            if (s != si) continue;
            if (before) newmethod = newmethod.InsertNodesBefore(si, linelist);
            else newmethod = newmethod.InsertNodesAfter(si, linelist);
            break;
        }

        break;
    }

    return newmethod;
}

I need to replace all such comments in my method. This function only inserts node, and does it only one time.

Edit. On this moment, I've got the following solution, but it seems too complicated and not obvious...

   public static MethodDeclarationSyntax getChangedNode(MethodDeclarationSyntax method)
    {
        var TestEntryArgName = "__tst";
        /* Adding last param */
        var parlist = method.ChildNodes().OfType<ParameterListSyntax>().First();
        var newparlist = parlist.AddParameters(SyntaxFactory.Parameter(
                                                                SyntaxFactory.Identifier(TestEntryArgName))
                                                                                .WithType(SyntaxFactory.ParseTypeName("Heap ")));
        var newmethod = method.ReplaceNode(parlist, newparlist);

        /* Adding __tst.AddElement(i); */
        while (true) {
            IEnumerable<SyntaxNode> desc;
            bool triviaFound;
            desc = newmethod.Body.DescendantNodes();
            triviaFound = false;
            foreach (var s in desc)
            {
                SyntaxTrivia st = SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ");
                bool fl = false;
                bool before = true;
                var lt = s.GetLeadingTrivia();

                foreach (var triviaEntry in lt)
                {
                    if (triviaEntry.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia)
                    {
                        fl = true;
                        st = triviaEntry;
                        break;
                    }
                }

                if (!fl)
                {
                    lt = s.GetTrailingTrivia();
                    before = false;
                    foreach (var triviaEntry in lt)
                    {
                        if (triviaEntry.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia)
                        {
                            fl = true;
                            st = triviaEntry;
                            break;
                        }
                    }
                    if (!fl) continue;
                }

                var commentContents = st.ToString();
                char[] delim = { ' ', '\n', '\t', '\r' };
                var ar = commentContents.Split(delim, StringSplitOptions.RemoveEmptyEntries);
                if (ar.Length != 2 || ar[0] != "add") continue;

                var lineToAdd = TestEntryArgName + ".AddElement(" + ar[1] + ")";
                var linelist = new List<ExpressionStatementSyntax>();
                linelist.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression(lineToAdd)));

                var childlist = s.Parent.ChildNodes();

                foreach (var si in childlist)
                {
                    if (s != si) continue;
                    if (before) newmethod = newmethod.InsertNodesBefore(si, linelist);
                    else newmethod = newmethod.InsertNodesAfter(si, linelist);
                    break;
                }

                var newTrvias = newmethod.DescendantTrivia().Where((t) =>
                                {
                                    if (t.Kind() != SyntaxKind.SingleLineDocumentationCommentTrivia)
                                        return false;
                                    var arr = t.ToString().Split(delim, StringSplitOptions.RemoveEmptyEntries);
                                    return arr.Length == 2 && arr[0] == "add";
                                });

                newmethod = newmethod.ReplaceTrivia(newTrvias.First(), SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "));
                triviaFound = true;
                break;
            }
            if (!triviaFound) break;
        }
        return newmethod;
    }
like image 429
Ilya Muradyan Avatar asked Jul 17 '16 20:07

Ilya Muradyan


2 Answers

What You are looking for is probably CSharpSyntaxRewriter.This is a class in roslyn that visits each node, token and trivia in syntactic model of Your code. You can make your own rewriter, that overrides VisitTrivia and returns the expression You want to get.For example:

    public class MyRewriter : CSharpSyntaxRewriter
{
public MyRewriter(): base(true)
{
}
public override SyntaxTrivia VisitTrivia(SyntaxTrivia trivia)
{
    if(trivia.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia)
    {
        string xml = trivia.ToFullString();

        var TestEntryArgName = "__tst";

        char[] delim = { ' ', '\n', '\t', '\r' };
        var ar = xml.Split(delim, StringSplitOptions.RemoveEmptyEntries);
        if (ar.Length != 3 || ar[1] != "add")
        {
            return base.VisitTrivia(trivia);
        }
        var lineToAdd = TestEntryArgName + ".AddElement(" + ar[2] + ")";

        var expression = SyntaxFactory.SyntaxTrivia(SyntaxKind.SingleLineCommentTrivia, lineToAdd);

        return expression;
    }
    return base.VisitTrivia(trivia);
    }
}

sample use:

    var myRewriter = new MyRewriter();

    string code = "";

    using (StreamReader sr = new StreamReader("Program.cs"))
    {
        code = sr.ReadToEnd();
    }
    var tree = CSharpSyntaxTree.ParseText(code);
    var node = tree.GetRoot();


    using(StreamWriter sw = new StreamWriter("Program.cs"))
    {
        sw.Write(myRewriter.Visit(node));
    }
like image 68
Hubert Avatar answered Oct 24 '22 09:10

Hubert


I found a simple solution, and I think, it suits me, but, I know, it can not be called a "right" solution, because it deals with the source code. Anyway:

public static MethodDeclarationSyntax getChangedNode(MethodDeclarationSyntax newmethod)
{
    /* Adding __tst.AddElement(i); */
    while (true)
    {
        var desc = newmethod.Body.DescendantTrivia().Where((t) => t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia));
        var triviaFound = false;

        foreach (var s in desc)
        {
            var commentContents = s.ToString();
            char[] delim = { ' ', '\n', '\t', '\r' };
            var ar = commentContents.Split(delim, StringSplitOptions.RemoveEmptyEntries);
            if (ar.Length != 2 || ar[0] != "add") continue;

            var lineToAdd = "\r\n__tst.AddElement(" + ar[1] + ");\r\n";
            newmethod = CSharpSyntaxTree.ParseText(newmethod.GetText().Replace(s.FullSpan, lineToAdd))
                .GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().First();
            triviaFound = true;
            break;
        }
        if (!triviaFound) break;
    }
    return newmethod;
}
like image 33
Ilya Muradyan Avatar answered Oct 24 '22 09:10

Ilya Muradyan