Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing multiple nodes in Roslyn syntax tree

Tags:

c#

roslyn

I'm trying to replace a couple of nodes in a syntax tree using roslyn. But the immutable nature of it seems to get in my way.

    public static string Rewrite(string content)
    {
        var tree = CSharpSyntaxTree.ParseText(content);
        var root = tree.GetRoot();

        var methods =root
            .DescendantNodes(node=>true)
            .OfType<MethodDeclarationSyntax>()
            .ToList();

        foreach(var method in methods)
        {
            var returnActions = method
                .DescendantNodes(node => true)
                .OfType<BinaryExpressionSyntax>()
                //Ok this is cheating
                .Where(node => node.OperatorToken.ValueText == "==")
                .Where(node => node.Right.ToString() == "\"#exit#\"" || node.Right.ToString() == "\"#break#\"")
                .Select(node => node.Parent as IfStatementSyntax)
                .ToList();

            var lookup = new Dictionary<StatementSyntax,StatementSyntax>();

            if (returnActions.Count > 0)
            {
                foreach(var ifStatement in returnActions)
                {
                    var mainCall = ifStatement.GetPrevious() as ExpressionStatementSyntax;                        
                    var newIfStatement = ifStatement.WithCondition(mainCall.Expression.WithoutTrivia());

                    lookup[mainCall] = null;
                    lookup[ifStatement] = newIfStatement;
                }

                //this only replace some of the nodes
                root = root.ReplaceNodes(lookup.Keys, (s, d) => lookup[s]);
            }
        }

        return root.ToFullString();
    }

The problem is that when I call root.ReplaceNodes only some of the nodes gets replaced.

I guess that the replacement changes the tree so that the other nodes no longer match the original tree and thus cant be replaced.

But what is the best way to deal with this?

Looping over the process over and over untill no more change occurs feels lame :)

The changes can occur nested, and I think that is what causes the problems here. Can I sort the changeset somehow to get around this or is there an idiomatic way to go about things here?

like image 904
Roger Johansson Avatar asked Jul 09 '15 11:07

Roger Johansson


1 Answers

I guess that the replacement changes the tree so that the other nodes no longer match the original tree and thus cant be replaced.

You're right. Replacing nodes creates entirely new syntax trees. Nodes from previous syntax trees cannot be compared against these new syntax trees.

There are four ways to apply multiple changes to a syntax tree:

  1. Use the DocumentEditor - See: https://stackoverflow.com/a/30563669/300908
  2. Use Annotations (Lines 236 and 240)
  3. Use .TrackNodes()
  4. Create a CSharpSyntaxRewriter that replaces nodes in a bottom-up approach. I've written about this on my blog.

Of these options, I believe the DocumentEditor has the reputation for being the easiest to use. It may very well be the idiomatic way to apply multiple changes going forward.

like image 53
JoshVarty Avatar answered Nov 06 '22 19:11

JoshVarty