Привет, Хабр!
Не так давно я сходил на конференцию CLRium от sidristij, где увидел довольно простой и удобный способ для анализа исходного кода C# в MSVS 2015.
Задача взята из проекта, в котором я участвую: каждый аргумент со ссылочным типом должен иметь аттрибут NotNull или CanBeNull (которые потом использует ReSharper). В реальности, конечно, в самом проекты атрибуты являются только частью проверок, однако это не мешает им быть обязательными. Уже есть тесты, которые проверяют сборку и падают, если методы или конструкторы не содержат требуемых атрибутов, однако разработчики все равно довольно часто забывают их проставить, что приводит к падениям билдов, обновлению кода и т.д. Вот если бы Visual Studio вместе с ReSharper выдавали бы предупреждения, что код не совсем хороший, то можно было бы сэкономить время и нервы…
И, на самом деле, вместе с новой студией это становится возможным! Более того, сделать свои проверки нереально просто.
Исходный код можно посмотреть тут.
Итак, для начала у Вас должна быть скачана Microsoft Visual Studio 2015. Советую внимательно выбирать язык, так как я не посмотрел и скачал русскую версию, с весьма нестандартным переводом.
Далее, необходимо скачать расширение для MSVS ".NET Compiler Platform SDK Templates" (см. детали здесь, спасибо Ordos)
Далее создаем проект для для нашего анализатора кода:
![](https://habrastorage.org/files/69b/454/b31/69b454b319bc4a1ca3e52caa5458d35e)
В результате, студия создаст три проекта: рабочий проект, тесты и специальный проект для 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.
Получается следующий код:
Все, теперь наш маленький анализатор уже заставит 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:
![](https://habrastorage.org/files/848/a11/7c1/848a117c15dc4daa940a3e694281d3f0)
В итоге, на выходе мы получаем расширение:
1. Которое подключается и обновляется через Nuget
2. Проверяет код в процессе написание и компиляции (в т.ч. без MSVS)
3. Пишется настолько просто и быстро, что ревью среднего pull-request'а в компании займет больше времени
И напоследок парочка советов:
1. Как Вы видите, Microsoft отдала предпочтение неизменяемым типам. А потому большинство конструкций Code Fix можно создать заранее, а потом просто давать ссылки.
2. В процессе тестирования проверять можно легко только один файл, а потому лучше специально предусмотреть варианты с partial-классами и прочими многофайловыми конструкциями
3. Пока нет Release версии Roslyn, а потому API может незначительно поменяться. Уже сейчас некоторые ответы на Stackoverflow содержат советы по устаревшему API.
4. Ordos подсказал страницу в интернете с похожим описанием.
Не так давно я сходил на конференцию 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 подсказал страницу в интернете с похожим описанием.