Roslyn - 如何将变量声明语句插入脚本的开头(在使用之后)?

时间:2016-12-10 19:33:19

标签: c# refactoring roslyn

我的目标是解析用户通过Roslyn提供的C#脚本文件。 让我们假设最终用户提供如下脚本:

using System;
return "Hello";

我正在寻找一种在任何给定脚本中尽可能早地插入一些变量初始化语句的通用方法。 根据我的理解,这几乎是在最后一次使用声明之后。

为了举例,我们假设我只需要插入" var xyz = 123;"在最早的位置。因此,在这种情况下,最终结果应该是

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

我怎么能这样做?

我尝试了以下内容;

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();

当我尝试直接替换为

时出现同样的问题
root.InsertNodesAfter(last_using, my_statement);

而不是DocumentEditor。

为什么这会失败?我不确定为什么它试图将我的语句转换为using指令 - 我是否只能追加相同类型的节点?!

有人可以给我一个如何实现这一目标的指针吗?

非常感谢!

1 个答案:

答案 0 :(得分: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 });

InsertNodesBeforeInsertNodesAfter处理节点列表,因此要在其之前或之后添加的节点必须位于列表中,并且要插入的节点必须来自相同的类型。

该方法的评论提到它(但不是那么清楚)

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

如果需要,请参阅实际进行替换的source code