I'm trying to replace nodes within a syntax tree in Roslyn, and it's just about working, but with an annoyance which feels it shouldn't be a problem.
The syntax tree is generated from a script, and I want the result to be a script-based syntax tree too - but for some reason, replacing a node in the tree creates a new syntax tree with changed options: the Kind
becomes Regular
instead of Script
. That's fixable with SyntaxTree.WithRootAndOptions
but it feels like I'm doing something wrong if I need to call that.
Sample program:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Linq;
class Program
{
static void Main(string[] args)
{
Script script = CSharpScript.Create("Console.WriteLine(\"Before\")",
ScriptOptions.Default.AddImports("System"));
var compilation = script.GetCompilation();
var tree = compilation.SyntaxTrees.Single();
var after = SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("After"));
var root = tree.GetRoot();
var before = root.DescendantNodes().OfType<LiteralExpressionSyntax>().Single();
var newRoot = root.ReplaceNode(before, after);
var fixedTree = newRoot.SyntaxTree.WithRootAndOptions(newRoot, tree.Options);
Console.WriteLine(newRoot); // Console.WriteLine("After")
Console.WriteLine(tree.Options.Kind); // Script
Console.WriteLine(newRoot.SyntaxTree.Options.Kind); // Regular
Console.WriteLine(fixedTree.Options.Kind); // Script
}
}
(Output is in comments.)
Is this workaround actually correct, or is there some different way I should be replacing the node in the tree?
When you replace nodes in a tree, you create a new sub-tree of nodes. Essentially, this new sub-tree is not contained within a SyntaxTree. However, the SyntaxTree property on the node conjurs up a new one if you ever observe it. At the time it does this, the original SyntaxTree is long gone, so its not possible to retain the parse options. Even if it were possible, retaining the options would be meaningless because you no longer have a tree produced by the parser.
The reason Roslyn creates this SyntaxTree is that so all sub-trees are technically contained within a SyntaxTree instance, so that Roslyn can associated diagnostics with it. This is useful if you use the SemanticModel's exploratory API's to attempt to bind & get semantic info for fragments of trees that are not currently part of the compilation. The diagnostic reports the error and its location, which denotes the tree instance it is within.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With