Перейти к содержанию

Правила подавления (Suppression)

Правила этой группы сообщают о конфигурации подавлений самого прогона, а не о коде, который он проанализировал. Значение suppress_paths или suppress_namespaces, не назвавшее ничего, ничего не прячет и не спрячет, тогда как автор считает, что прячет.


Конфигурация подавлений

Rule ID: suppression.configuration

Под самим именем suppression.configuration не сообщается ничего — оно существует для того, чтобы у каналов ниже был один владелец, которого можно выключить и настроить целиком. У каждого канала своё имя и свой смысл.

Что измеряется

Каждое значение подавления, полученное прогоном, — глобальное или под rules.<имя> — сверяется с тем, что прогон действительно увидел: suppress_paths с проанализированными файлами, suppress_namespaces с объявленными неймспейсами. Значение, не совпавшее ни с чем, сообщается.

У находки уровня проекта нет неймспейса, поэтому её не удаляет никакое значение suppress_namespaces — ни глобальное, ни под rules.<name>, — каким бы широким ни был его шаблон. (project), которое отчёт показывает на месте неймспейса такой находки, — отображаемое значение, а не неймспейс: записанное как suppress_namespaces: [{exact: '(project)'}] на любом из уровней, оно ничего не удаляет и сообщается здесь, как любое другое значение, не назвавшее ничего. Чтобы заглушить находку уровня проекта, выключите её канал или примите её в baseline.

Почему это важно

Это другой ноль, не тот, что уже показывает --format=suppressed. Список neverMatched в том отчёте строится из удалённых находок и отвечает «этот суппрессор ничего не удалил» — до такого состояния доходит и честное подавление, чей долг выплачен, и правильный ответ на него — порадоваться и удалить запись. Каналы здесь отвечают «этот суппрессор ничего не назвал», и никакой ремонт кода к этому не приводит. Приводит только опечатка, переименование или перенос.

Два вопроса выглядят в отчёте одинаково и ведут к противоположным действиям — поэтому отвечают на них раздельно.

Область и severity

Все каналы сообщаются на уровне проекта, severity warning.

Измеренный охват проекта задаёт два вопроса. Отсутствие неймспейсов требует полной области деклараций: пропущенные PHP, авторское снятие PHP, generated-исключения и неизвестный состав кода удерживают ответ. Path-значения требуют полноты PHP-путей и известности состава отдельно; авторские и generated-исключения сами по себе этот вопрос не закрывают. Namespace-значения rule-ledger также спрашивают отсутствие деклараций. Для литерального (exact или subtree) namespace-значения этот вопрос уже, см. Исключённый и generated-код.

Каждое значение размещается отдельно. Пригодные PSR-4 записи production и development из снимка вызова размещают неймспейсы независимо от report state; без карты они остаются unjudged. Literal под снятой записью не судится, как и удержанный удалённым кодом, который мог объявить его неймспейс. Path/ledger regex не судится при неполноте путей или авторском снятии, способном скрыть совпадение; namespace regex дополнительно требует полной области деклараций. Консервативный ответ может удержать устаревший regex рядом с несвязанным исключением: повторите прогон без исключения. Path-значение, не связанное ни с одним проанализированным файлом, не судится и тогда, когда его предмет может лежать в записи, которую прогон нашёл, но не проанализировал: в совпавшем с ним файле со сбоем или исключённом как @generated файле, либо в каталоге, который прогон не смог перечислить или по ссылке на который не пошёл, если литерал значения лежит под ним (regex-значение — под любым таким каталогом). Отчёт называет каждый пропуск как {channel, option, pattern}, включая состояние covered.

Исключённый и generated-код

Исключённые PHP-файлы и файлы @generated не анализируются, но прогон читает операторы namespace каждого из них (токен-скан, а для файлов больше 64 КиБ текстовый скан, который может удержать больше; без анализа) и судит литеральное значение exact или subtree в suppress_namespaces, глобальном или в rules.<name>, если только этот код не мог объявить неймспейс, который значение связывает. Поэтому устаревший exact: App\Does\Not\Exist сообщается, даже когда src/Kernel.php исключён; exact: App не судится, пока исключённый файл объявляет App. Объявленный неймспейс делает неразрешимыми и своих предков: файл в App\Foo\Bar удерживает exact: App\Foo. Решение о том, мог ли удалённый код объявить неймспейс, не учитывает регистр ASCII; само значение сопоставляется с учётом регистра, как настроено.

Path-значения (suppress_paths), названные файлом, исключённым как @generated, не судятся: файл найден намеренно не проанализированным, а не написан с ошибкой (см. Область и severity).

Что по-прежнему удерживается:

  • Все значения, когда удалённый код неизвестен: нечитаемый исключённый файл, исключённый именованный файл, являющийся симлинком, исключённый каталог, который не перечисляется или содержит ссылку на каталог либо нерегулярную .php-запись, имя в namespace, не заканчивающееся в пределах 1 КиБ, похожий на объявление оператор namespace, который скан не распознаёт, или прогон, превысивший лимит (2000 чтений файлов, 8 МиБ прочитанного, 20 000 просмотренных записей в удалённых каталогах).
  • regex-значения, как раньше.
  • Все namespace-значения, когда файл не прошёл анализ: прогон не увидел все объявленные неймспейсы.
  • Любая другая причина удержания (пропущенные PHP-пути, неизвестный состав проекта, отсутствие карты PSR-4) удерживает все значения, как раньше.
  • Аудиты неймспейсов architecture.*, cohesion.unmatched-exclude-method и coupling.unmatched-framework-namespace не сужены: любой удалённый PHP-файл по-прежнему их удерживает, потому что им нужны имена классов и методов, а не только неймспейсы.

check печатает предупреждение в stderr, когда удалённый код (исключение или фильтрация generated-файлов) один не дал судить литеральные namespace-значения подавлений. Оно называет число значений и их каналы; -q его гасит. Объявления нельзя отнести к отдельному селектору, поэтому называется каждое исключение с PHP-свидетельством; прочее удаление упоминается безымянно как «an exclude entry». Regex-значения, значения, не осуждённые по другой причине, и литералы, уже связанные анализируемыми неймспейсами, не считаются: литерал, совпавший с неймспейсом анализируемого кода, судится даже рядом с удалённым кодом. Совет зависит от причины:

Warning: Code removed by exclude "exact:src/Kernel.php" may declare the namespaces of 1 suppression value, so it was not judged (suppression.unmatched-namespace). Narrow the exclusion to judge it.
Warning: Code removed by exclude "exact:src/Kernel.php" could not be read in full (too many files, an unreadable file or a link) and may declare the namespaces of 1 suppression value, so it was not judged (suppression.unmatched-namespace). Exclude fewer files (and no unreadable file or link) to judge it.
Warning: Code removed by the generated-file policy may declare the namespaces of 1 suppression value, so it was not judged (suppression.unmatched-namespace). Pass --include-generated to judge it.

Если удалили и исключение, и generated-файлы, подлежащее читается «exclude "…" and the generated-file policy», а совет — «Narrow the exclusion and/or pass --include-generated to judge it». Исключение без PHP или удалившее код вне измеренного состава не называется: оно читается «an exclude entry» («another exclude entry» рядом с названным). Значение --suppress-namespace, переданное check, считается как настроенное. Для нескольких значений: «N suppression values, so they were not judged» и «to judge them».

directives и baseline:* такого предупреждения не печатают; directives по-прежнему называет suppression.unmatched-namespace в охвате проекта. Массив projectScope.unjudgedValues в отчёте перечисляет каждое значение без суждения во всех форматах.

В генерируемый baseline они не попадают: baseline:generate измеряет находки другим швом, и предупреждение о собственной конфигурации автора не должно становиться принятым долгом в файле, который автор генерирует одной командой.

Каждая находка заканчивается указанием конфигурации, записавшей значение, — Written by configuration file "vendor/acme/std/qmx.yaml" (imported by configuration file "qmx.yaml")., — так что значение из общей базы отличимо от записанного в проекте.

Значение, которое ничему не соответствует и которое записали только файлы, установленные пакетом Composer, — база, импортированная из каталога установки анализируемого проекта (vendor/, если config.vendor-dir не говорит иного), будь пакет копией или path-репозиторием через симлинк, — не судится, если проект не может его убрать: запись глобального suppress_paths или suppress_namespaces (следующий слой в эти списки только добавляет) или значение записи suppress_namespace_channels правила (её нельзя опустошить). Значение перечисляется в projectScope.unjudgedValues, а причина installed-configuration называет файл. Привязанное значение, значение, которое пишет и сам проект, и значение из командной строки судятся как обычно, как и suppress_paths и suppress_namespaces правила: список этого правила, записанный в вашем файле, [] в том числе, заменяет список базы.

Если значение корректно и исправить его нельзя — общий для нескольких репозиториев qmx.yaml, распространяемый не как установленный пакет и называющий путь, которого в одном из них нет, — есть два выхода, и они не одно и то же:

  • Выключить канал там, где живёт эта общая конфигурация: disabled_rules: ['suppression.unmatched-path'] гасит ровно один канал, два других продолжают говорить. --disable-rule делает то же для одного прогона.
  • Принять находку в baseline, написанный вручную. Записанная вручную запись под project:, называющая канал и occurrence находки, учитывается как любая другая. Поскольку значение входит в этот occurrence, принятие относится именно к этому значению: замена его другим непривязанным будет показана, а не пройдёт под принятой записью.

Каналы

Канал Что обнаруживает
suppression.unmatched-path Глобальное значение suppress_paths, не совпавшее ни с одним файлом
suppression.unmatched-namespace Глобальное suppress_namespaces, не совпавшее ни с одним неймспейсом
suppression.unmatched-rule-ledger То же для значения, заданного под rules.<имя>, включая suppress_namespace_channels — оно называется вместе с селектором, под которым написано

Канал реестра правила отделён потому, что у ошибки, которую он ловит, своя форма: подавление на правило пишется рядом с тем правилом, которому принадлежит, и значение, пережившее свой предмет, ещё долго читается в контексте как осмысленное.

Пример

# qmx.yaml
suppress_namespaces:
  - exact: App\Legacy\Importer   # неймспейс переименован в App\Import
[project] suppression.unmatched-namespace
  The suppress_namespaces pattern "App\Legacy\Importer" matched no namespace
  declared in this run, so it suppressed nothing and could not have. If the
  code it was written for still exists under another spelling, its findings
  are being reported.

Опции

Опция По умолчанию Описание
enabled true Включает или выключает правило и все его каналы

Конфигурация

# qmx.yaml
rules:
  suppression.configuration:
    enabled: true
bin/qmx check src/ --disable-rule=suppression.configuration

Владелец и namespace membership

Ключ suppress_namespace_channels обязан адресовать channel своего configured producer на Namespace level. Один channel свидетельствует и owner membership, и applied level; уровень sibling channel не оправдывает ключ. Keyed map пиши в YAML. Audit использует итоговый selection и сохраняет ограничения whole/partial coverage. enabled:true намеренно включает поверх нижнего disable.