使用预处理程序指令解析和生成代码

时间:2018-08-27 13:33:35

标签: c# roslyn

我正在尝试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被删除。

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

1 个答案:

答案 0 :(得分: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节点。

有趣的是,如果仅通过using指令之一传递.AddUsings(),则似乎省略了前导琐事。但是如果您给它一个包含多个UsingDirectiveSyntax的数组,那么除第一个数组外,每个数组都包含前导琐事。 (这可能不完全正确;我仅从黑盒观测中进行工作。)

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

因此,如果您想使用这些工具来生成要反馈到完整构建管道中的源代码,则可以将其视为错误(或者至少可以将其很容易成为错误源)。如果有预期的用法可以使您摆脱这些问题,那么我找不到直接的文档。在这种情况下,您可以从usings中删除琐事,然后再将其添加到输出中;但是在其他情况下,我认为这可能会丢失您想要保留的内容。