Правила аннотаций¶
Раньше встроенные аннотации @qmx-ignore и @qmx-threshold могли ломаться двумя тихими способами: аннотация, которая ни к чему не адресовалась (опечатка, удалённая метрика, имя правила там, где ожидалось имя канала), просто ничего не делала, а аннотация, которая когда-то что-то значила, но перестала совпадать с чем-либо в текущем прогоне, тоже не подавала никакого сигнала. Правила аннотаций закрывают оба пробела — теперь каждая написанная директива проверяется, и директива, которая не может делать то, что заявляет, публикуется как находка, а не молча игнорируется.
Валидация директив¶
Идентификатор правила: annotation.directive
Что измеряет¶
Каждая аннотация @qmx-ignore, @qmx-ignore-next-line, @qmx-ignore-file и @qmx-threshold, написанная в анализируемом коде, проверяется относительно собственной конфигурации прогона: существует ли имя, к которому она адресуется, тот ли это вид имени для данной директивы, и сделала ли она что-нибудь в этом прогоне?
Под именем продюсера annotation.directive никогда ничего не публикуется — оно существует только для того, чтобы у четырёх каналов ниже был один общий владелец для отключения и настройки как семьи. У каждого канала своё имя правила и свой смысл.
Почему это важно¶
Аннотация — это утверждение о коде: «эта находка ожидаема и принята». Когда утверждение неверно — имя написано с опечаткой, правило переименовано, метрика, на которую оно ссылается, удалена из конфигурации, — прежнее поведение состояло в том, чтобы промолчать. Это хуже обычного ложноотрицательного результата: рецензент, видящий @qmx-ignore, считает, что подавление работает, тогда как на самом деле не подавляется ничего. Громкий отказ на сломанной директиве и рутинное напоминание об убранной директиве, которая тихо перестала что-либо значить, сохраняют доверие к слою аннотаций.
Четыре канала¶
| Канал | Значение | Вид | Severity |
|---|---|---|---|
annotation.unresolved-directive |
Директива называет то, к чему ей не разрешено адресоваться, — опечатка, имя правила там, где ожидался канал, wildcard X.*, ничему не соответствующий, или повисшая ссылка на вычисляемую метрику, убранную из конфигурации. Здесь же публикуются способы сломать директиву ещё до того, как дело дойдёт до канала: тег @qmx-, который инструмент не читает, тег без своего канала или правила, порог вне докблока и декларационная форма, написанная там, где ничего не измеряется. |
Ошибка конфигурации | Error, не настраивается |
annotation.unsupported-threshold |
@qmx-threshold адресуется к правилу, которое не объявляет поддержку переопределения порогов. |
Ошибка конфигурации | Error, не настраивается |
annotation.invalid-threshold |
Сам payload @qmx-threshold некорректен — неверная форма или неразбираемое значение для опций этого правила. |
Ошибка конфигурации | Error, не настраивается |
annotation.unused-directive |
Директива корректна и адресуется к чему-то реальному, но ничего из адресованного не сработало в этом прогоне. | Обычный долг | Warning по умолчанию, настраивается через unused-directive-severity |
Первые три — ошибки конфигурации: они сообщают об ошибке в том, что написано, а не о долге в анализируемом коде. Как и архитектурные диагностики конфигурации (см. Правила архитектуры), они валят прогон безусловно, как только срабатывают — fail_on для них не учитывается вообще, даже fail_on: none, — и ни одну из них нельзя принять в baseline или заглушить ещё одним @qmx-ignore. Опция severity на любой из них выглядела бы переключателем поведения, ничего при этом не меняя, — поэтому её ни у одной из трёх нет.
annotation.unused-directive устроена иначе: директива была корректной и когда-то что-то значила, просто ничего не подавила в этом конкретном прогоне; threshold отдельно оценивает аудит. Это обычный долг по уборке, а не ошибка — у неё настраиваемая severity, её можно принять в baseline, убрать корневым suppress_paths и сузить git-скоупом как любую другую находку. Двух исключений, которые есть у других каналов, для неё нет — и не было ещё до запрета: корневой suppress_namespaces до неё не достаёт, потому что субъект находки — файл, в котором написана аннотация, и неймспейса, с которым можно сравнивать, у него нет; а собственные suppress_paths / suppress_namespaces правила её не видят, потому что этот канал прогон собирает уже после исполнения правил, когда поканальный леджер исключений закрыт. Выключение всего правила annotation.directive её убирает — вместе с тремя диагностиками-ошибками конфигурации выше.
Единственное, чего с ней сделать нельзя, — подавить директивой. @qmx-ignore, @qmx-ignore-next-line и @qmx-ignore-file отвергаются, если их цель достаёт до annotation.unused-directive: по точному имени, через annotation.* или с :file после любого из них. Отказ публикуется как annotation.unresolved-directive на строке самой директивы. Директива, спрятавшая этот канал, спрятала бы ответ на вопрос, ради которого канал существует. Голый @qmx-ignore-file без канала не отвергается — отвергать нечего, он ничего не называет, — но и канал он больше не гасит.
Формы, которые директивой так и не становятся¶
Прочитанный, но недопустимый тег сохраняется как место отказа. Неизвестные
имена тегов, отсутствие аргументов, точный тег посреди текста, незакрытый блок,
threshold вне docblock и декларационная форма без корректной привязки дают
annotation.unresolved-directive. Отказ привязки не обрывает анализ файла.
Теги — @qmx-ignore, @qmx-ignore-next-line, @qmx-ignore-file и
@qmx-threshold. Первым двум нужен канал на строке тега, threshold — правило
и значения. Закрытие комментария не считается аргументом.
Подавление декларации может привязываться к class-like декларации, callable, свойству, константе, enum case или параметру с границами из канонической таблицы. Threshold нужен docblock и измеряемая декларация, пороги которой он меняет; обычные свойства, константы, enum cases и параметры не меняют пороги содержащей декларации.
Прямые значения-замыкания и стрелочные функции привязываются, в том числе после
ключа массива или имени аргумента. Обёртка, лишь содержащая замыкание, не
привязывается: перенеси комментарий перед function/fn.
Docblock во всём заголовке декларации, включая промежутки атрибутов и между
ключевым словом и именем, принадлежит декларации. Физическим формам file и
next-line привязка к декларации не нужна.
Как цитировать директиву¶
Используй на одной строке парные последовательности обратных кавычек равной длины без текста перед тегом внутри пары либо закрытый блок обратных кавычек/ тильд для многострочных примеров. Одиночная кавычка и неравные длины ничего не цитируют; незакрытый блок даёт отказы для строк с директивами. Точные правила открытия, закрытия и опечаток смотри в грамматике строки.
Отказы аудита и общие подавления¶
Каждый прочитанный тег даёт одно место; физическая позиция различает одинаковые
комментарии на строке. Место отказа имеет effect: "refused" и непустой
refusals: [{channel, message}]; остальные исходы несут refusals: [].
Unmeasured теперь означает выключенный producer или перекрытый threshold.
Аудит возвращает 2 за публикуемый отказ или наблюдаемую inert-директиву;
неполный анализ сначала возвращает 4. Внутренние адреса селекции не входят
в JSON.
Общие * и голый file получают обычную оценку effective/inert. Они
по-прежнему не глушат annotation.unused-directive или duplication.clone.
Явный селектор этих каналов получает отказ после проверки достижимости
декларации/уровня.
Пример¶
complexity не называет ни правила, ни канала — это голый префикс, а голый
префикс больше не является группой. @qmx-ignore всегда адресует канал
(violationCode) — точно либо в форме X.*. Это публикует
annotation.unresolved-directive с сообщением, называющим ближайшие
допустимые каналы:
@qmx-threshold всегда адресуется к правилу, никогда к каналу, суженному
до уровня — coupling.cbo:class называет правило coupling.cbo на его
уровне class, а порог не различает уровни. Это тоже публикует
annotation.unresolved-directive:
@qmx-threshold "coupling.cbo:class" addresses a rule at a level, and a threshold addresses the producing rule by its own name: it does not distinguish levels (ADR 0024). Retune the whole rule "coupling.cbo", or set the level alone with --rule-opt coupling.cbo:class.<option>=<value>.
/**
* @qmx-ignore complexity.ccn:callable reason="stable for now"
*/
public function calculateShipping(Order $order): float
{
// метод был позже упрощён ниже порога сложности
}
Аннотация корректна и когда-то подавляла реальную находку, но
calculateShipping() больше не срабатывает по complexity.ccn на уровне
callable. Это публикует annotation.unused-directive с severity Warning —
напоминание удалить бесполезную аннотацию, а не ошибка конфигурации.
@qmx-ignore-file — единственная форма, у которой канал необязателен, поэтому
слово сразу после тега по-настоящему неоднозначно: это может быть канал, а
может быть первое слово причины. Оно читается как канал, Generated ничему
не адресуется, и это публикует annotation.unresolved-directive:
Suppression "Generated" addresses no channel. No declared name is close to it. Prose belongs after "--".
Напишите -- перед прозой, чтобы сказать «дальше идёт причина»:
-- обязателен только для этого неоднозначного случая. У @qmx-ignore и
@qmx-ignore-next-line аргумент канала обязателен и всегда идёт первым,
поэтому @qmx-ignore complexity.ccn:callable Legacy state machine
однозначен и без разделителя — хотя написать -- и там тоже полезно, чтобы
все три тега читались одинаково.
Как исправить¶
annotation.unresolved-directive— исправьте имя. Используйте точное имя канала для@qmx-ignore(илиX.*для всех потомковX), и точное имя правила для@qmx-threshold. Если сломалась аннотация на вычисляемую метрику, либо верните метрику вcomputed_metrics:, либо удалите повисшую аннотацию. Если сообщение указывает на первое слово вашей причины — вы написали@qmx-ignore-fileсразу с прозой без канала; добавьте--перед причиной (см. пример выше). Если сообщение говорит, что тег — не из тех, что инструмент читает, исправьте написание по четырём перечисленным тегам. Если оно говорит, что тег не называет канал или не называет правило (names no channel/names no rule), допишите аргумент на строке тега. Если оно говорит, что тег ни к чему не привязывается или ничего не перенастраивает, декларационная форма стоит там, где ничего не измеряется: перейдите на@qmx-ignore-next-lineили перенесите тег на класс, метод или функцию, о которых он; порог, написанный в строчном или блочном комментарии, место которому в докблоке этого объявления, — см. раздел «Формы, которые директивой так и не становятся» выше.annotation.unsupported-threshold— удалите@qmx-threshold; у целевого правила нет опций, которые может переопределить порог. Посмотрите разделOptions(«Настройки») на странице этого правила, чтобы узнать, что оно принимает.annotation.invalid-threshold— приведите payload к форме опций правила (см. разделConfiguration(«Конфигурация») этого правила для ожидаемых ключей и типов значений).annotation.unused-directive— удалите аннотацию. Она ничего не делает, и её присутствие вводит следующего читателя в заблуждение, будто находка всё ещё подавляется. Если удалить пока нельзя — примите находку в baseline или исключите путь; ещё одна@qmx-ignoreв список вариантов не входит и будет отвергнута.
Область учёта¶
Учёт annotation.unused-directive намеренно узок, чтобы никогда не порождать шум из-за конфигурационных решений, которые и так работают как задумано:
- Учитываются только директивы, адресующиеся к включённым правилам. Отключение целой семьи правил — как это делает встроенный пресет
legacy— не делает её аннотации «неиспользуемыми». - Учитываются только директивы внутри анализируемого набора файлов. Сужение анализа через
--report=git:stagedили подобное не помечает аннотации в файлах вне этого прогона. @qmx-thresholdникогда не участвует в учёте unused-directive — переопределение порога либо корректно и молчаливо, либо является ошибкой конфигурации под одним из первых трёх каналов.- Одна написанная директива даёт ровно одну находку. Аннотация
@qmx-ignoreна уровне класса, которая также привязывается к каждому методу внутри класса, не печатается по разу на метод; субъект находки всегда — файл, потому что именно там аннотация физически написана.
Валидация происходит после разрешения конфигурации
Множество каналов, используемое для валидации, строится из уже разрешённой конфигурации самого прогона, включая семью вычисляемых метрик (health.* и любые метрики computed.*, которые определяет проект). Поэтому @qmx-ignore health.cohesion резолвится точно так же, как статически объявленный канал. Отсюда два следствия: удаление вычисляемой метрики из конфигурации превращает каждую ссылавшуюся на неё аннотацию в ошибку annotation.unresolved-directive, так же как опечатку; а @qmx-threshold на отключённом правиле корректен и молчалив — включённость — это фильтр исполнения, а не факт о существовании имени правила.
Граница health для класса¶
Шесть встроенных правил health принимают границу в docblock класса, например
@qmx-threshold health.cohesion 40. Укажи одно число либо одинаковые значения
warning и error; неполная пара или разные значения вызывают отказ. Граница
действует только на это точное объявление класса и учитывает направление метрики
из конфигурации. Пороги неймспейса и проекта остаются в computed_metrics.
Общий producer computed не поддерживает inline-пороги.
Аудит того, что директива всё ещё делает¶
Каналы выше отвечают на вопрос, адресуема ли директива: называет ли она что-нибудь и погасило ли подавление хоть что-то. Ни один из них не отвечает, что делает @qmx-threshold, потому что ничто из публикуемого правилом не говорит, с какой границей оно решало.
Это отдельно и сразу для обеих форм отвечает bin/qmx directives: каждая пороговая директива снимается поодиночке, и правила исполняются заново поверх измерений того же прогона. В qmx check этого нет — одно исполнение правил на директиву обычному прогону платить незачем, — и запускать команду предполагается осознанно или отдельным шагом CI.
Настройки¶
| Опция | По умолчанию | Описание |
|---|---|---|
enabled |
true |
Включить или выключить валидацию директив целиком. |
unused-directive-severity |
warning |
Severity для annotation.unused-directive. Допустимо: info, warning, error. |
У остальных трёх каналов — annotation.unresolved-directive, annotation.unsupported-threshold и annotation.invalid-threshold — нет опции severity: они валят прогон безусловно, так же как пять архитектурных диагностик конфигурации (см. Правила архитектуры).
Конфигурация¶
# qmx.yaml
rules:
annotation.directive:
enabled: true
unused-directive-severity: info # сохранить прежнюю severity Info
Допуск directive diagnostics¶
Integer-boundary annotation требует integer: дробь даёт annotation.invalid-threshold вместо усечения. Unresolved и unused directive channels выбираются напрямую; unsupported/invalid threshold errors следуют выбранному addressed producer. Явный disable annotation.directive выключает весь producer. Existing enabled:true examples — намеренные точные включения; см. selection.