Isolin
Isolin

Reputation: 876

C# CodeAnalysis - Structure of expressions

I want to do a simple code replacement in a C# (.NET 4.5) script in VS2013. Each @GetIt call should be rewritten so that it is encapsulated in a lambda function:

new MyClass(@GetInt("a") * @GetInt("b"));

becomes

new MyClass( x => (x.GetInt("a") * x.GetInt("b")) )

I installed the CodeAnalysis of Roslyn to parse the scripts. I use the following call to get all tokens with the @GetInt identifier:

var getters = CSharpSyntaxTree.ParseText("new MyClass(@GetInt("a") * @GetInt("b"));")
                              .GetRoot().DescendantTokens().OfType<SyntaxToken>()
                              .Where(x => x.Text.Equals("@GetInt"));

For @GetInt as a simple parameter of the MyClass constructor it works fine and using getters[i].Parent.Parent.Parent.Parent I correctly get to the MethodDeclarationSyntax node of the constructor.

However, adding the multiplication as in the example on the top, token for the second @GetInt declares * GetInt("b") as its parent (it is howevver already a MethodDeclarationSyntax, not a parameter node) and traversing back to its parent leads to the CompilationUnitSyntax which is the root!

This way, I get no information about the position of the second @GetInt in the syntax tree. Consequently the replacement is not possible without the missing information.

I have checked the case without using the prefix @, but the result was the same. Can someone please tell me whether I'm doing something wrong?

SOLUTION as suggested by JoshVarty in one of his comments: Until the code analysis of Scripts is disabled in the NuGet package, I have to use a workaround. First, I take the string script = "..." and decorate it to

var decoratedScript = "class MYCLASS { void METHOD() {\n" + script + "\n} }";

After the rewriting of @GetInt is done, I remove the added decorations.

Upvotes: 2

Views: 562

Answers (1)

JoshVarty
JoshVarty

Reputation: 9426

By default ParseText() expects you to be passing in a typical C# document. (Something comprised of using statements, namespaces, types etc.)

If you'd like to parse individual expressions you can use CSharpParseOptions to do so:

var parseOptions = CSharpParseOptions.Default;
parseOptions = parseOptions.WithKind(SourceCodeKind.Script); //We're going to be passing individual expressions in.

var getters = CSharpSyntaxTree.ParseText(@"new MyClass(@GetInt(""a"") * @GetInt(""b""));", parseOptions)
                  .GetRoot().DescendantTokens().OfType<SyntaxToken>()
                  .Where(x => x.Text.Equals("@GetInt"));

When I use this, I get both invocations of @GetInt as a child of a BinaryExpressionSyntax (The multiplication).

You can see the tree was originally parsed incorrectly for yourself via:

var tree = CSharpSyntaxTree.ParseText(@"new MyClass(@GetInt(""a"") * @GetInt(""b""));");
var errs = tree.GetDiagnostics().Where(n => n.Severity == DiagnosticSeverity.Error);

Edit Really sorry, it appears that the scripting APIs were removed for RTM. The only work around I can think of for you to try is to wrap every statement in a class and a method until this starts working again.

The reason it's present on http://sourceroslyn.io is because that's based off the current master and they're re-adding it in.

Upvotes: 2

Related Questions