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

时间:2017-05-18 21:44:01

标签: c# roslyn using-directives

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

工作代码处理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的方法 - 这可能会很遗憾,因为其他一切似乎与脚本相当好......

1 个答案:

答案 0 :(得分:12)

据我所知,脚本引擎中的默认编译不会为除语法错误之外的任何内容配置诊断。不幸的是,脚本引擎只有自己配置底层编译的有限选项。

但是,您可以通过跳过脚本引擎并直接自己创建编译来实现您的目标。这基本上是脚本宿主在幕后做的事情,添加了一些编译的默认值以及一些奇特的东西,如提升类声明。跳过脚本宿主并自己创建编译的代码如下所示:

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语句的所需诊断信息。