3

I'm aiming to parse C# script files supplied by users via Roslyn. Let's assume the end user provides a script like:

using System;
return "Hello";

I'm looking for a general way to insert a few variable initialization statements at the earliest possible location in any given script. To my understanding, that would pretty much be after the last using statement.

For the sake of the example, let's assume I just need to insert "var xyz = 123;" at the earliest location. So the end result should, in this case, be

using System;
var xyz = 123;
return "Hello";

How could I do that?

I tried the following;

Solution solution = new AdhocWorkspace().CurrentSolution;
var project = solution.AddProject("projectName", "assemblyName", LanguageNames.CSharp)
     .WithMetadataReferences(new[] {MetadataReference.CreateFromFile(typeof(object).Assembly.Location) })
     .WithParseOptions(new CSharpParseOptions(kind: Microsoft.CodeAnalysis.SourceCodeKind.Script));

// scriptCode contains the user script input, e.g.:
// using System;
// return "Hello";
Document document = project.AddDocument("SCRIPT-TEMP-DOCUMENT.cs", scriptCode);
var root = document.GetSyntaxRootAsync().Result;

var my_statement = SyntaxFactory.ParseStatement("var xyz = 123;");

// will return the node: "using System;"
var last_using = root.DescendantNodes().Where(x => x is UsingDirectiveSyntax).Last();

var documentEditor = DocumentEditor.CreateAsync(document).Result;
documentEditor.InsertAfter(last_using, my_statement);

// This step will throw an exception:
// An exception of type 'System.InvalidCastException' occurred in System.Core.dll but was not handled in user code
// non-English message, so losely translated --> Additional information:  object of type "Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax" cannot be converted to "Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax"
var newDocument = documentEditor.GetChangedDocument();

Same issue when I try directly replacing with

root.InsertNodesAfter(last_using, my_statement);

instead of the DocumentEditor.

Why does this fail? I'm not sure why its trying to cast my statement into a using directive - can I only append nodes of the same type?!

Could anybody give me a pointer of how to achieve this best?

Thanks a lot!

2
  • I haven't messed with Roslyn, so this is just a guess, but it could be that because C# doesn't allow variable declarations after using directives (such a thing would be global variables, which C# doesn't support). So when it parses that line, it is expecting another using directive (or namespace declaration) but is getting a variable declaration instead, which it doesn't like. Commented Dec 10, 2016 at 20:10
  • 1
    Usually yes - however that is valid syntax when parsed as a script. Since I explicitly specified SourceCodeKind.Script in the project parse options, I would assume that syntax transformations valid under these rules should (hopefully) work? Commented Dec 10, 2016 at 20:12

1 Answer 1

1
SyntaxTree tree = CSharpSyntaxTree.ParseText(
    @"using System;
    return 1;", new CSharpParseOptions(LanguageVersion.CSharp6, DocumentationMode.Parse, SourceCodeKind.Script)
);

var root = (CompilationUnitSyntax)tree.GetRoot();
var global = SyntaxFactory.GlobalStatement(SyntaxFactory.ParseStatement("var xyz = 123;"));
root = root.InsertNodesBefore(root.Members.First(), new SyntaxNode[] { global });

InsertNodesBefore and InsertNodesAfter work on list of nodes, so the node that you want to add before or after it, must be within a list, and the node you want to insert, must be from the same type.

The comment of the method mention it (but its no so clear)

/// <param name="nodeInList">The node to insert after; a descendant of the root node an element of a list member.</param>

See the source code that actually do the replacing if you want.

Sign up to request clarification or add additional context in comments.

2 Comments

Amazing. Thanks a lot, very much appreciate your solution!
@Dudi - how do we make InsertNodesAfter or Before work in case of multiple diagnostics? Because if the code fix provider executes the action for any one of the flagged diagnostics, the location of other similar diagnostics become invalid.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.