Всем привет. Перед вами небольшая статья о добавлении анализа Blazor компонентов в PVS-Studio. По ходу рассказа постараемся предугадать ваши немые вопросы по теме и ответить на них. Приятного прочтения!
О чем статья?
Мы научили PVS-Studio анализировать код внутри компонентов Blazor приложений. В статье расскажем, как появилась идея, чем такой анализ полезен пользователям, какие возможности и ограничения есть в текущей реализации. Также предлагаем обсудить эту и будущие фичи PVS-Studio (об этом в конце статьи).
Давайте для начала освежим в памяти терминологию.
Razor – это синтаксис разметки, позволяющий встроить в веб-страницу C# код.
Разметку Razor используют Blazor/Razor компоненты (.razor файлы), а также Razor Pages (.cshtml файлы). Я указываю двойное название Blazor/Razor т.к. согласно MSDN оба названия приемлемы: "Blazor apps are built using Razor components, informally known as Blazor components".
Итак, наш разговор пойдет в первую очередь о файлах с расширением .razor.
.razor файл – основной строительный элемент Blazor приложения. Razor компоненты позволяют инкапсулировать логику и отображение для формирования элемента пользовательского интерфейса. Каждый Razor компонент является самостоятельным блоком, что позволяет использовать их для создания новых компонентов и переносить между проектами.
Зачем мне, как пользователю, анализ Razor компонентов?
Web-приложения, использующие Blazor, содержат в файлах .razor C# код. Этот код, как и любой другой, может содержать ошибки, недочеты и опечатки. Они в свою очередь могут быть трудны для поиска "вручную" и дороги в исправлении при обнаружении на поздних этапах разработки. Теперь же PVS-Studio анализирует Razor компоненты, помогая сохранять код в них безопасным и корректным.
Почему раньше не умели анализировать .razor файлы?
Анализ .razor файлов был проблемой из-за наличия конструкций разметки Razor. Они не позволяли корректно получить синтаксическую и семантическую модели соответственно. Из-за этого проверка .razor файлов не была доступна "из коробки". Теперь мы доработали анализатор и научили проверять C# код в этих файлах. На данном этапе анализ производится только в блоках @code{...}. Такое решение было принято по совокупности различных факторов. Основной — желание предоставить пользователям анализ Razor компонентов как можно быстрее, пусть и в виде MVP.
Почему решились взяться за анализ .razor файлов?
Размышляя о том, куда должен двигаться наш C# анализатор, мы раз за разом приходили к тому, что вектор развития должен соответствовать потребностям пользователей. Многие из них так или иначе связаны с web-разработкой. Поэтому мы подумали: "А почему бы не расширить функционал анализатора в области анализа web-проектов?". И, вуаля – анализ .razor файлов добавлен.
И что, в .razor файлах могут быть ошибки?
Да, могут. Давайте рассмотрим несколько наиболее наглядных ошибок в .razor файлах Open Source проектов, которые попались мне при беглой проверке.
Issue 1:
....
RenderFragment<T> child() => item =>
@<text>
@{
var rowClass = new CssBuilder(RowClass)
.AddClass(RowClassFunc?.Invoke(item, rowIndex))
.AddClass(customClass,
!string.IsNullOrEmpty("mud-table-row-group-indented-1"))
.Build();
....
}
....
Сообщение анализатора: V3022 Expression '!string.IsNullOrEmpty("mud-table-row-group-indented-1")' is always true.
Начнем с довольно интересного на мой взгляд срабатывания анализатора в проекте MudBlazor. Анализатор предупреждает нас о том, что выражение !string.IsNullOrEmpty("mud-table-row-group-indented-1") всегда истинно. Спорить с этим вряд ли получится, так как строковый литерал не может стать пустым или обратиться в null. Тяжело сказать, что именно здесь имелось ввиду, но выглядит подозрительно...
Issue 2:
@code
{
....
public void Evaluate()
{
....
var exp = new Expression(CalcExpression);
var result = exp.Eval();
if (result == double.NaN)
{
Current = "ERROR";
return;
}
Current = Math.Round( result,8).ToString(CultureInfo.InvariantCulture);
CalcExpression = Current;
}
....
}
Сообщение анализатора: V3076 Comparison of 'result' with 'double.NaN' is meaningless. Use 'double.IsNaN()' method instead.
Рассмотрим другой кейс на том же проекте. Анализатор говорит, что сравнение с double.NaN – бессмысленно. Но почему? Согласно MSDN сравнение двух NaN значений через оператор '==' всегда возвращает false. Для корректного сравнения необходимо использовать метод double.IsNaN.
Issue 3:
Ну и куда же без упоминания собственных ошибок. Да-да, мы тоже допускаем ошибки и находим их благодаря собственному анализатору. После добавления анализа .razor файлов мы обнаружили интересные срабатывания в коде наших внутренних утилит.
@code
{
async Task Load()
{
var exceptionCustomers = string.Empty;
var exceptionTeam = string.Empty; <=
....
(CustomerNotifier, exceptionCustomers) = DbProvider.GetMailApplication(
CustomerCoreLibrary
.Data.Types
.ConfigMailApplicationType
.CustomerNotifier);
if (!string.IsNullOrEmpty(exceptionCustomers))
await MatDialogService.AlertAsync(exceptionCustomers);
(TeamNotifier, exceptionCustomers) = DbProvider.GetMailApplication(
CustomerCoreLibrary
.Data.Types
.ConfigMailApplicationType
.TeamNotifier);
if (!string.IsNullOrEmpty(exceptionTeam)) <=
await MatDialogService.AlertAsync(exceptionTeam);
....
}
}
Сообщения анализатора:
V3022 Expression '!string.IsNullOrEmpty(exceptionTeam)' is always false.
V3127 Two similar code fragments were found. Perhaps, this is a typo and 'exceptionTeam' variable should be used instead of 'exceptionCustomers'.
Здесь анализатор выдал два предупреждения, которые вместе полноценно описывают сложившуюся проблему.
Во-первых, было обнаружено, что условие !string.IsNullOrEmpty(exceptionTeam) всегда ложно. Действительно, переменной exceptionTeam присвоена пустая строка и далее, вплоть до проверки, значение переменной никак не изменяется. Но из-за чего возник такой участок кода? На этот вопрос отвечает второе предупреждение, предполагающее наличие опечатки и использование не той переменной. И правда! Стопроцентное попадание.
На этом предлагаю остановиться, ведь у нас здесь не проверка проектов, а статья о новых возможностях анализатора. Если хотите поискать ошибки в .razor файлах своих проектов, можете взять пробную версию анализатора здесь. Интересными находками делитесь в комментариях.
Что с поддержкой .cshtml файлов?
На данный момент анализ .cshtml файлов не реализован, но это не значит, что мы не возьмемся за это. Если увидим заинтересованность пользователей, то обязательно попробуем поддержать и этот формат. Если вам интересен анализ .cshtml файлов, напишите нам об этом.
Заключение
В заключении прошу вас пройти небольшой опрос по теме в конце статьи и поделиться своим мнением о Razor и Blazor в комментариях.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Aleksey Avdeev. PVS-Studio now analyzes Blazor components.