C# · 12月 31, 2021

c# – 如何使用Roslyn检测脚本(而不是文档)中未使用的导入?

我正在编写一个系统来处理作为Noda Time的单元测试编写的片段,因此我可以将该片段包含在文档中.我有一个 first pass工作,但我想整理代码.处理代码段时需要做的其中一件事情就是确定该代码段实际需要哪些using指令. (单个源文件中可以有多个片段,但每个代码片段都将单独显示在文档中 – 我不希望从一个片段导入影响另一个片段.)

工作代码处理文档实例 – 我创建一个单独的文档,每个片段包含单个方法和所有潜在的导入,将其添加到项目中,然后删除不必要的使用指令,如下所示:

private async static Task<Document> RemoveUnusedImportsAsync(Document document){ var compilation = await document.Project.GetCompilationAsync(); var tree = await document.GetSyntaxTreeAsync(); var root = tree.GetRoot(); var unusedImportNodes = compilation.GetDiagnostics() .Where(d => d.Id == “CS8019”) .Where(d => d.Location?.sourceTree == tree) .Select(d => root.FindNode(d.Location.sourceSpan)) .ToList(); return document.WithSyntaxRoot( root.RemoveNodes(unusedImportNodes,SyntaxRemoveOptions.KeepNoTrivia));}

我了解到,在使用文档时我可以使用IOrganizeImportsService,但是我想把它写成一个脚本,因为它以各种方式变得更干净.

创建脚本很容易,所以我只想分析一下未使用的导入(在一些早期的清理步骤之后).这里的代码我希望工作的脚本:

private static Script RemoveUnusedImports(Script script){ var compilation = script.GetCompilation(); var tree = compilation.SyntaxTrees.Single(); var root = tree.GetRoot(); var unusedImportNodes = compilation.GetDiagnostics() .Where(d => d.Id == “CS8019”) .Where(d => d.Location?.sourceTree == tree) .Select(d => root.FindNode(d.Location.sourceSpan)) .ToList(); var newRoot = root.RemoveNodes(unusedImportNodes,SyntaxRemoveOptions.KeepNoTrivia); return CSharpScript.Create(newRoot.ToFullString(),script.Options);}

不幸的是,这根本没有发现任何诊断 – 它们只是在编译时才产生:(

以下是一个简短的示例应用程序:

using System;using Microsoft.CodeAnalysis.CSharp.Scripting;using Microsoft.CodeAnalysis.Scripting;class Program{ static void Main(string[] args) { string text = @”using System;using System.Collections.Generic;Console.WriteLine(“”I only need to use System””);”; Script script = CSharpScript.Create(text); // Not sure whether this *should* be required,but it doesn’t help… script.Compile(); var compilation = script.GetCompilation(); foreach (var d in compilation.GetDiagnostics()) { Console.WriteLine($”{d.Id}: {d.GetMessage()}”); } }}

必需包:Microsoft.CodeAnalysis.CSharp.Scripting(例如v2.1.0)

这不产生任何输出:(

我的猜测是这是打算的,因为脚本通常有不同的用例.但是,有没有办法为脚本目的启用更多诊断?还是有一些替代方法来检测脚本中的未使用的导入?如果没有,我会回到我的基于文档的方法 – 这很可惜,因为其他一切似乎都很好地与脚本工作…

解决方法 据我所知,脚本引擎中的默认编译没有配置除了语法错误之外的任何诊断.不幸的是,脚本引擎只有自己配置底层编译的选择有限.

但是,您可以通过跳过脚本引擎并自行直接创建编译来实现您之前所做的工作.这实际上是脚本主机在幕后添加了一些编译默认值以及一些奇怪的东西,如提升类声明.跳过脚本主机并自己创建编译的代码看起来像:

using System;using System.IO;using System.Reflection;using Microsoft.CodeAnalysis;using Microsoft.CodeAnalysis.CSharp;class Program{ static void Main(string[] args) { string text = @”using System;using System.Collections.Generic;Console.WriteLine(“”I only need to use System””);”; SyntaxTree SyntaxTree = CSharpSyntaxTree.ParseText(text,new CSharpParSEOptions(kind: SourceCodeKind.Script)); var coreDir = Path.GetDirectoryName(typeof(object).GetTypeInfo().Assembly.Location); var mscorlib = MetadataReference.CreateFromFile(Path.Combine(coreDir,”mscorlib.dll”)); var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); var compilation = CSharpCompilation.Create(“MyAssembly”) .AddSyntaxTrees(SyntaxTree) .AddReferences(mscorlib) .WithOptions(options); foreach (var d in compilation.GetDiagnostics()) { Console.WriteLine($”{d.Id}: {d.GetMessage()}”); } }}

您会注意到这会产生一些关于缺少引用的不合需要的诊断信息,因此编译引用需要稍加调整以包含默认库(您可以在上面查看mscorlib的模式).您还应该看到关于未使用的语句的所需诊断.