Пишем простой Roslyn анализатор для C#

Привет, друзья. На этой неделе я пытался найти, как заблокировать использование определенной библиотеки (Newtonsoft.Json) в моем проекте. К моему удивлению, я на нашел способа из коробки, поэтому пришлось учиться, как писать свой Roslyn анализатор (в простонародье, кастомный линтер).

Пара слов про мою проблему. Есть приложение, состоящее из нескольких dotnet проектов. Приложение использует System.Text.Json для сериализации Json. С другой стороны, большое количество зависимостей проекта (библиотек) использует Newtonsoft.Json, поэтому мы получаем Newtonsoft в проекте транзитивно. Не хочется допустить того, чтобы люди случайно начали использовать Newtonsoft в нашем коде (а это очень просто, потому что обе библиотеки используют крайне схожие имена классов и методов, типа JsonSerializer). С другой стороны, я не могу просто удалить Newtonsoft из зависимостей проекте, так как он используется нашими библиотеками/зависимостями.

Изначально я думал найти какой-то способ запрета использования библиотеки в моем коде на уровне MSBuild. Если я ничего не упускаю, то такого способа нет. Самое близкое — есть обсуждение на гитхабе про эту проблему, но решения пока нет — https://github.com/dotnet/sdk/issues/11803.

Также есть способ ручного парсинга *.sln, или *.csproj файлов с помощью библиотеки Microsoft.Build (SolutionFile.Parse(…)). По сути, вы можете прочитать все csproj файлы вашего проекта, и попытаться найти, или не найти там нежеланную зависимость. Проблема этого способа в том, что я получаю библеотеку в проекте неявно/транзитивно, и у меня нет никаких ссылок на Newtonsoft из csproj файлов. Выходит, что этот способ для меня тоже не работает.

Про последний способ мне подсказали — можно написать свой простой линтер, используя API компилятора Roslyn. Всё, что мне надо — это анализировать все using в моей кодовой базе, и если там есть что-то про Newtonsoft, то нужно ломать билд:

    private static void AnalyzeUsing(SyntaxNodeAnalysisContext context)
    {
        var usingDirective = (UsingDirectiveSyntax)context.Node;
        string directive = usingDirective.Name.ToString();
        if (directive.IndexOf("newtonsoft", StringComparison.OrdinalIgnoreCase) >= 0)
        {
            var diagnostic = Diagnostic.Create(Rule, usingDirective.GetLocation(), directive);
            context.ReportDiagnostic(diagnostic);
        }
    }

На самом деле, код довольно простой. Полный код можно найти тут — https://github.com/Hixon10/NewtonsoftJsonUsageAnalyzer.

Главная сложность, которая у меня была — а как вообще писать свой анализатор (какой тип проекта создавать, как заставить компилятор его использовать, как собрать nuget пакет, и тд). К счастью, обе IDE — VisualStudio и JetBrains Rider имеют готовый шаблон для Roslyn анализаторов. Из него вы сразу получаете рабочий пример с тестами (в случае студии вы даже получаете сборку nuget пакета). Всё, что вам остается сделать — это написать логику вашего анализатора. К моему удивлению, весь процесс крайне прямолинейный, что обычно редкость, когда речь заходит об каких-то плагинах к компилятору.

Я собариюсь использовать этот анализатор в своем проекте. Если я наткнусь на какие-то неожиданные проблемы, я либо обновлю этот пост, либо создам еще одну запись.

Категории: Программирование