Подтвердить что ты не робот

Как я могу обнаружить неиспользованный импорт в Script (а не в документе) с Roslyn?

Я пишу систему для обработки фрагментов, написанных как единичные тесты для Noda Time, поэтому я могу включить фрагменты в документацию. У меня есть первый проход, но я хотел привести в порядок код. Одна из вещей, которые это необходимо сделать при обработке фрагмента, - это то, что из директив 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, так как он чувствует себя намного чище разными способами.

Создание script очень просто, поэтому я хотел бы просто проанализировать, что для неиспользуемых импортов (после некоторых предыдущих шагов очистки). Здесь код, который, как я надеялся, будет работать для 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, что было бы очень жалко, поскольку все остальное, по-видимому, прекрасно работает со сценариями...

4b9b3361

Ответ 1

Насколько я знаю, компиляция по умолчанию в механизме сценариев не настраивает диагностику ни для чего, кроме синтаксических ошибок. К сожалению, у механизма сценариев только ограниченные возможности для настройки основной компиляции.

Тем не менее, вы, вероятно, можете достичь того, что вам нужно, пропустив скриптовый движок и непосредственно создав компиляцию самостоятельно. Это, по сути, то, что хост script делает за кулисами с добавлением некоторых значений по умолчанию для компиляции, а также несколько причудливых вещей, таких как объявления классов класса. Код, чтобы пропустить хост script и создать компиляцию самостоятельно, будет выглядеть примерно так:

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 выше). Вы также должны увидеть нужную диагностику и о неиспользуемых инструкциях.