【问题标题】:Roslyn - how to insert variable declaration statements into beginning of script (after usings)?Roslyn - 如何将变量声明语句插入脚本的开头(在使用之后)?
【发布时间】:2016-12-10 19:33:19
【问题描述】:

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

using System;
return "Hello";

我正在寻找一种通用方法,以便在任何给定脚本的最早可能位置插入一些变量初始化语句。 据我了解,这几乎是在最后一个 using 语句之后。

为了这个例子,假设我只需要插入“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 指令 ​​- 我只能附加相同类型的节点吗?!

谁能告诉我如何做到最好?

非常感谢!

【问题讨论】:

  • 我没有和 Roslyn 搞过,所以这只是一个猜测,但可能是因为 C# 不允许在使用指令后声明变量(这样的东西是全局变量,C#不支持)。因此,当它解析该行时,它期待另一个 using 指令(或命名空间声明),但却得到一个它不喜欢的变量声明。
  • 通常是的 - 但是当解析为脚本时这是有效的语法。由于我在项目解析选项中明确指定了 SourceCodeKind.Script,我会假设在这些规则下有效的语法转换应该(希望)有效?

标签: c# refactoring roslyn


【解决方案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

【讨论】:

  • 太棒了。非常感谢,非常感谢您的解决方案!
  • @Dudi - 我们如何让 InsertNodesAfter 或 Before 在多个诊断的情况下工作?因为如果代码修复提供程序为任何一个标记的诊断执行操作,其他类似诊断的位置将变为无效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多