【问题标题】:parsing and generating code with preprocessor directive使用预处理器指令解析和生成代码
【发布时间】:2019-02-02 01:39:40
【问题描述】:

我正在试验 roslyn,解析和生成 c# 代码。我试图弄清楚CSharpSyntaxTree.ParseText 方法如何处理预处理器符号。

这是我的测试方法。它接受一些 C# 代码作为字符串,提取 using 语句并返回带有这些 using 语句的新字符串,同时考虑到预处理器指令。

private static string Process(string input, string[] preprocessorSymbols)
{
    var options = CSharpParseOptions.Default.WithPreprocessorSymbols(preprocessorSymbols);
    var syntaxTree = CSharpSyntaxTree.ParseText(input, options);
    var compilationUnit = (CompilationUnitSyntax)syntaxTree.GetRoot();
    var usings = compilationUnit.Usings.ToArray();
    var cs = SyntaxFactory.CompilationUnit()
            .AddUsings(usings)
            .NormalizeWhitespace();
    var result = cs.ToString();
    return result;
}

当使用以下输入提供此方法时,它按预期工作:

var input = "using MyUsing1;\r\nusing MyUsing2;";
string result = Process(input, new[] { "" });
Assert.AreEqual("using MyUsing1;\r\nusing MyUsing2;", result);

当添加预处理器指令,但没有将所述指令传递给解析器时,结果仍然如预期(条件using 语句被剥离):

var input =
    "using MyUsing1;\r\n" +
    "#if CONDITIONAL\r\n" +
    "using MyUsing2;\r\n" +
    "#endif";
string result = Process(input, new[] { "" });
Assert.AreEqual("using MyUsing1;", result);

但是,当将 CONDITIONAL 预处理器指令添加到 CSharpParseOptions 时,我得到了一个奇怪的结果

var input = 
    "using MyUsing1;\r\n" +
    "#if CONDITIONAL\r\n" +
    "using MyUsing2;\r\n" +
    "#endif";
string result = Process(input, new[] { "CONDITIONAL" });
Assert.AreEqual("using MyUsing1;\r\nusing MyUsing2;", result); // fails??

实际返回值为"using MyUsing1;\r\n#if CONDITIONAL\r\nusing MyUsing2;"#if CONDITIONAL 部分被保留,#endif 被删除。

这是一个错误,还是我做错了什么?

【问题讨论】:

  • 你看过LINQPad吗,它可以显示脚本的语法树。

标签: c# roslyn


【解决方案1】:

为了理解这种行为,我添加了另一个测试用例来考虑:

var input =
    "using MyUsing1;\r\n" +
    "#if CONDITIONAL\r\n" +
    "using MyUsing2;\r\n" +
    "#endif" +
    "using MyUsing3;\r\n";
string result = Process(input, new[] { "CONDITIONAL" });

在这种情况下,#if#endif 都被保留了。

如果您中断调试器并查看usings 数组,似乎每个UsingDirectiveSyntax 都知道using 语句(Span)的最小字符范围和“更宽”范围来自原始流 (FullSpan) 中的字符,其中包括诸如 #if 指令之类的内容。

再深入一点,文档将 preproc 指令等前面的代码称为“领先的琐事”,并将其作为子节点附加到 using 节点。

有趣的是,如果你只传递了 .AddUsings() 一个 using 指令,它似乎省略了前面的琐事;但是如果你给它一个包含多个UsingDirectiveSyntaxs 的数组,那么对于除了第一个之外的每个,它都包括领先的琐事。 (这可能不完全正确;我只是根据黑盒观察工作。)

我不会假装理解这种行为的原因。结果是许多看起来不错的代码(例如您的示例)将产生令人不安的输出。 (如果你传入new[] {usings[0], usings[2], usings[1]},你会得到更糟糕的输出,#endif#if 之前。但是......你知道......我猜你为什么要这样做?)

因此,如果您想使用这些工具生成源代码以反馈到完整的构建管道中,您可能会将其视为错误(或者至少是很容易成为错误来源的奇怪行为)。如果有预期的用途可以让你清楚这一点,我找不到它的直接文档。在这种情况下,您可以先从usings 中删除琐事,然后再将它们添加到输出中;但在其他情况下,我认为这可能会丢失您想要保留的东西。

【讨论】:

  • 感谢您的帮助。同时,通过重新阅读the docs,发现指令是“琐事”并且总是与 next 行相关联。这个用例很奇怪,但按我想的设计工作......
猜你喜欢
  • 1970-01-01
  • 2018-01-03
  • 1970-01-01
  • 2021-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多