如何使用 Roslyn 检测脚本(而不是文档)中未使用的导入?
How can I detect unused imports in a Script (rather than a Document) with Roslyn?
我正在编写一个系统来处理作为 Noda Time 单元测试编写的片段,因此我可以将这些片段包含在文档中。我有一个 first pass 工作,但我想整理代码。处理片段时需要做的一件事是计算出该片段实际上需要哪些 using
指令。 (单个源文件中可以有多个片段,但每个片段将单独出现在文档中 - 我不希望从一个片段中导入影响另一个片段。)
working 代码处理 Document
个实例 - 我为每个片段创建一个单独的 Document
,包含一个方法和所有潜在的导入,添加它到项目中,然后像这样删除不必要的 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
,但我 喜欢 将其写成 Script
,因为它在各种方面感觉更干净。
创建脚本很简单,所以我只想分析未使用的导入(在一些较早的清理步骤之后)。这是我希望 可用于脚本的代码:
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)
这不会产生任何输出:(
我猜这是故意的,因为脚本 通常 有不同的用例。但是有什么方法可以为脚本目的启用更多诊断吗?或者是否有其他方法可以检测 Script
中未使用的导入?如果没有,我将回到我的基于 Document
的方法——这将是一种遗憾,因为其他一切似乎都与脚本一起工作得很好......
据我所知,脚本引擎中的默认编译除了语法错误外不配置任何诊断。不幸的是,脚本引擎只有有限的选项来自行配置底层编译。
但是,您可能可以通过跳过脚本引擎并直接自己创建编译来实现您想要的。这本质上是脚本宿主在幕后所做的,添加了一些编译默认值以及一些奇特的东西,例如提升 class 声明。跳过脚本宿主并自己创建编译的代码如下所示:
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 的模式)。您还应该看到有关未使用的 using 语句的所需诊断信息。
我正在编写一个系统来处理作为 Noda Time 单元测试编写的片段,因此我可以将这些片段包含在文档中。我有一个 first pass 工作,但我想整理代码。处理片段时需要做的一件事是计算出该片段实际上需要哪些 using
指令。 (单个源文件中可以有多个片段,但每个片段将单独出现在文档中 - 我不希望从一个片段中导入影响另一个片段。)
working 代码处理 Document
个实例 - 我为每个片段创建一个单独的 Document
,包含一个方法和所有潜在的导入,添加它到项目中,然后像这样删除不必要的 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
,但我 喜欢 将其写成 Script
,因为它在各种方面感觉更干净。
创建脚本很简单,所以我只想分析未使用的导入(在一些较早的清理步骤之后)。这是我希望 可用于脚本的代码:
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)
这不会产生任何输出:(
我猜这是故意的,因为脚本 通常 有不同的用例。但是有什么方法可以为脚本目的启用更多诊断吗?或者是否有其他方法可以检测 Script
中未使用的导入?如果没有,我将回到我的基于 Document
的方法——这将是一种遗憾,因为其他一切似乎都与脚本一起工作得很好......
据我所知,脚本引擎中的默认编译除了语法错误外不配置任何诊断。不幸的是,脚本引擎只有有限的选项来自行配置底层编译。
但是,您可能可以通过跳过脚本引擎并直接自己创建编译来实现您想要的。这本质上是脚本宿主在幕后所做的,添加了一些编译默认值以及一些奇特的东西,例如提升 class 声明。跳过脚本宿主并自己创建编译的代码如下所示:
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 的模式)。您还应该看到有关未使用的 using 语句的所需诊断信息。