Как стать автором
Обновить

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

Время на прочтение5 мин
Количество просмотров14K
Привет, Хабр!

Не так давно я сходил на конференцию CLRium от sidristij, где увидел довольно простой и удобный способ для анализа исходного кода C# в MSVS 2015.

Задача взята из проекта, в котором я участвую: каждый аргумент со ссылочным типом должен иметь аттрибут NotNull или CanBeNull (которые потом использует ReSharper). В реальности, конечно, в самом проекты атрибуты являются только частью проверок, однако это не мешает им быть обязательными. Уже есть тесты, которые проверяют сборку и падают, если методы или конструкторы не содержат требуемых атрибутов, однако разработчики все равно довольно часто забывают их проставить, что приводит к падениям билдов, обновлению кода и т.д. Вот если бы Visual Studio вместе с ReSharper выдавали бы предупреждения, что код не совсем хороший, то можно было бы сэкономить время и нервы…

И, на самом деле, вместе с новой студией это становится возможным! Более того, сделать свои проверки нереально просто.

Исходный код можно посмотреть тут.

Итак, для начала у Вас должна быть скачана Microsoft Visual Studio 2015. Советую внимательно выбирать язык, так как я не посмотрел и скачал русскую версию, с весьма нестандартным переводом.
Далее, необходимо скачать расширение для MSVS ".NET Compiler Platform SDK Templates" (см. детали здесь, спасибо Ordos)
Далее создаем проект для для нашего анализатора кода:



В результате, студия создаст три проекта: рабочий проект, тесты и специальный проект для vsix расширения. Первый проект необходим для реализации нашего анализатора + для создания Code Fix'ов, то есть подсказок в студии с предложением исправить. Есть два способа распространения пакета: через vsix расширения и через nuget. Первый позволяет установить проверки для Visual Studio на текущем компьютере, не затрагивая проекты. Второй способ позволяет проверять код во время разработки (причем, на любом компьютере, nuget пакет докачается), а также во время сборки (даже если Visual Studio не установлена), он работает и в предыдущих выпусках Visual Studio. Для создания nuget пакета достаточно просто nuspec файла и пары скриптов, однако для vsix расширения создается дополнительный проект (который здесь приведен только для примера).

Также интересен проект с тестами: мы можем тестировать и отлаживать наш анализатор без отдельного запуска Visual Studio! Мы просто создаем C# файл, загружаем его в компилятор, а он возвращает список Warning/Error!

Изначально Visual Studio создает шаблонный анализатор, который требует, чтобы все типы имели именования в UPPERCASE. И тесты заточены на него.

Чтобы поменять поведение, проделаем следующие изменения с классом наследником DiagnosticAnalyzer:

1. Изменим DiagnosticId на свой. Это ключ нашего warning'а (с типом String). Его увидит программист в списке ошибок, на него среагирует CodeFix, его будет использовать атрибут SuppressMessage. Чтобы случайно ни с кем не пересечься, лучше всего выбрать название подлиннее. Я выбрал его как <имя nuget пакета>_<внутренний id>: NullCheckAnalyzer_MethodContainsNulls
2. Затем лучше всего поменять все описания на свои: см. методы Title, MessageFormat, Description, Category.
3. Далее в методе Initialize поменяем аргументы функции RegisterSymbolAction: мы будем реагировать не на типы, а на методы. Кстати говоря, судя по моим изысканиям и исходникам Roslyn, часть значений SymbolKind вообще не поддерживается (то есть, например, на параметры мы реагировать не можем).
3. Меняем немного метод AnalyzeSymbol:
— На вход нам придет лексема для проверки
— На каждую ошибку необходимо добавить в контекст информацию о ней. То есть, для одного метода можно найти сколько угодно ошибок, причем с разными Id.

Получается следующий код:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class NullCheckAnalyzer : DiagnosticAnalyzer
{
    public const string ParameterIsNullId = "NullCheckAnalyzer_MethodContainsNulls";

    // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
    internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
    internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
    internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
    internal const string Category = "Naming";

    internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(ParameterIsNullId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

    private static readonly ImmutableArray<DiagnosticDescriptor> supportedDiagnostics = ImmutableArray.Create(Rule);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => supportedDiagnostics;

    public override void Initialize(AnalysisContext context)
    {
        context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
    }

    private static void AnalyzeSymbol(SymbolAnalysisContext context)
    {
        var methodSymbol = context.Symbol as IMethodSymbol;

        if (ReferenceEquals(null, methodSymbol) || methodSymbol.DeclaredAccessibility == Accessibility.Private)
        {
            return;
        }

        foreach (var parameter in ParametersGetter.GetParametersToFix(methodSymbol))
        {
            var type = methodSymbol.ContainingType;

            // For all such symbols, produce a diagnostic.
            var diagnostic = Diagnostic.Create(Rule, parameter.Locations[0], methodSymbol.Name, type.Name, parameter.Name);

            context.ReportDiagnostic(diagnostic);
        }
    }
}

Все, теперь наш маленький анализатор уже заставит Visual Studio сыпать ошибками. Для проверки запустим тесты. Microsoft заботливо создала целых два: проверка того, что пустой файл корректен, и проверка того, что диагностика + исправления работают правильно. Первый завершается правильно, а второй с ошибкой, так как мы так и не сделали Code Fix.

Я попытался быстро сделать Code Fix и понял, что даже такое простое исправление уже содержит нюансы, которые не так просто решить:
— Из какого namespace добавлять NotNull атрибут? Из Resharper.*? А если есть несколько вариантов: свои атрибуты и пакет от Resharper?
— Как дописывать using: внутри namespace, или же сверху файла? Не будет ли коллизий? Возможно, лучше зарегистрировать alias?
— Если нет ссылки на сборку с атрибутами, то её надо добавить, однако по каким правилам? Взять первую попавшуюся, или попробовать загрузить с сайта nuget? Или с корпоративного nuget репозитория?

Попробовав несколько исправлений, я понял, что:
1. Они работают. Roslyn действительно добавляет атрибуты, компилирует результат.
2. Алгоритм довольно простой: надо найти необходимый *Syntax элемент, потом с помощью SyntaxFactory создать правильный и вызвать ReplaceNode.
3. Правильный Code Fix не настолько прост, как кажется на первый взгляд. И вместо того, чтобы предлагать проблемное решение, лучше попросить программиста сделать исправление самостоятельно.

Для того чтобы протестировать анализатор, достаточно просто установить Nuget пакет (т.е. ввести команду в Package Manager Console: Install-Package NullCheckAnalyzer). Однако, скорее всего, тот пакет, который Вы собрали, не заработает, так как изначально PowerShell скрипты содержат ошибку: в путь в dll с анализатором зачем-то добавляется подпапка «C#». Поэтому эти строчки лучше поменять так, как сделано тут. После этих действий nuget пакет готов, его можно выгружать на nuget.org, а потом добавить в проект.

И вот как оно выглядит в Microsoft Visual Studio 2015:


В итоге, на выходе мы получаем расширение:
1. Которое подключается и обновляется через Nuget
2. Проверяет код в процессе написание и компиляции (в т.ч. без MSVS)
3. Пишется настолько просто и быстро, что ревью среднего pull-request'а в компании займет больше времени

И напоследок парочка советов:
1. Как Вы видите, Microsoft отдала предпочтение неизменяемым типам. А потому большинство конструкций Code Fix можно создать заранее, а потом просто давать ссылки.
2. В процессе тестирования проверять можно легко только один файл, а потому лучше специально предусмотреть варианты с partial-классами и прочими многофайловыми конструкциями
3. Пока нет Release версии Roslyn, а потому API может незначительно поменяться. Уже сейчас некоторые ответы на Stackoverflow содержат советы по устаревшему API.
4.
Ordos подсказал страницу в интернете с похожим описанием.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+21
Комментарии9

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн