Problem
PHP files in directories that PHPStan never analyses silently escape ALL static-analysis rules. A real example: in a downstream project a $_ENV['X'] ?? '' silent-default smell lived in config/services.php and was never caught — even though ForbidNullCoalescingEmptyStringRule was enabled — because php-qa-ci's PHPStan runner only analyses src and tests, not config. The rule was active; the directory was simply never visited. This is a whole class of blind spot: any dir with .php files that isn't in the analysed set gets zero enforcement, invisibly.
Why this canNOT be a normal PHPStan (AST) rule
A PHPStan rule only fires on nodes inside files PHPStan actually visits. An un-analysed directory is never visited, so a rule placed there can never fire — chicken-and-egg. The enforcement must therefore be a standalone check/tool that runs as part of the php-qa-ci pipeline and inspects the project structure vs the analysis configuration, not the parsed AST.
Proposed solution
Add a php-qa-ci pipeline check (e.g. a tool runnable via qa -t stanPathCoverage, name open to maintainers) that:
- Enumerates every directory in the project that contains at least one
.php file (excluding vendor/, build/cache output, and generated code by default).
- Reads the project's declared PHPStan analysis configuration — the analysed paths plus an explicit exclude list.
- FAILS the pipeline if any PHP-containing directory is neither explicitly included in the analysed paths nor explicitly excluded — forcing a conscious include/exclude decision for every such directory.
Intended default policy (overridable per project): vendor/ → exclude; src/, tests/, config/ → include. The key property is that adding a new top-level PHP dir (or a config/ that was silently unanalysed) trips the check until someone explicitly classifies it.
Acceptance criteria
- A new check integrated into the php-qa-ci pipeline (and into the relevant meta-groups, e.g.
allStatic).
- Project-level configuration to declare include/exclude classifications (with sensible defaults).
- The check fails with a clear, actionable message naming each unclassified PHP directory.
- Tests covering: a fully-classified project (passes); a project with an unclassified PHP dir like
config/ (fails); explicitly-excluded dirs (passes).
- Docs describing configuration and rationale.
Context / origin
Surfaced during downstream work (checkout "plan 00097" — Zoho dual-account credentials) when a $_ENV[...] ?? '' smell in a vendor config/services.php slipped past an enabled ForbidNullCoalescingEmptyStringRule purely because config/ was outside the analysed paths. The fix for that one instance was applied directly; this issue is the "defence before fix" ratchet to prevent the entire class recurring across all php-qa-ci consumers.
Problem
PHP files in directories that PHPStan never analyses silently escape ALL static-analysis rules. A real example: in a downstream project a
$_ENV['X'] ?? ''silent-default smell lived inconfig/services.phpand was never caught — even thoughForbidNullCoalescingEmptyStringRulewas enabled — because php-qa-ci's PHPStan runner only analysessrcandtests, notconfig. The rule was active; the directory was simply never visited. This is a whole class of blind spot: any dir with.phpfiles that isn't in the analysed set gets zero enforcement, invisibly.Why this canNOT be a normal PHPStan (AST) rule
A PHPStan rule only fires on nodes inside files PHPStan actually visits. An un-analysed directory is never visited, so a rule placed there can never fire — chicken-and-egg. The enforcement must therefore be a standalone check/tool that runs as part of the php-qa-ci pipeline and inspects the project structure vs the analysis configuration, not the parsed AST.
Proposed solution
Add a php-qa-ci pipeline check (e.g. a tool runnable via
qa -t stanPathCoverage, name open to maintainers) that:.phpfile (excludingvendor/, build/cache output, and generated code by default).Intended default policy (overridable per project):
vendor/→ exclude;src/,tests/,config/→ include. The key property is that adding a new top-level PHP dir (or aconfig/that was silently unanalysed) trips the check until someone explicitly classifies it.Acceptance criteria
allStatic).config/(fails); explicitly-excluded dirs (passes).Context / origin
Surfaced during downstream work (checkout "plan 00097" — Zoho dual-account credentials) when a
$_ENV[...] ?? ''smell in a vendorconfig/services.phpslipped past an enabledForbidNullCoalescingEmptyStringRulepurely becauseconfig/was outside the analysed paths. The fix for that one instance was applied directly; this issue is the "defence before fix" ratchet to prevent the entire class recurring across all php-qa-ci consumers.