fix(phpmd): scope the lib/Migration UnusedFormalParameter exclusion to its own ruleset - #739
Conversation
…o its own ruleset The nested <exclude-pattern> inside the UnusedFormalParameter <rule> was inert: PHPMD 2.15 honours exclude-patterns only as direct children of <ruleset>, so lib/Migration was scanned by the very rule the pattern was written to spare. Hoisting the pattern to the top level of phpmd.xml would have worked but is applied at file-collection time, dropping lib/Migration from EVERY rule and silently swallowing real complexity, StaticAccess and method-length findings. UnusedFormalParameter now lives alone in phpmd-unusedparams.xml with a top-level */Migration/* exclude, and the phpmd composer script runs both legs keeping the worst exit code.
Quality Report — ConductionNL/procest @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue3-compile | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 550/550 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 15:40 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/procest @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue3-compile | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 550/550 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 20:31 UTC
Download the full PDF report from the workflow artifacts.
|
Held, not merged — blocked by a repo-wide gate-24 wiring failure, not by anything in this PR. Hydra Gates is red here, but the failure is "a gate did not run", not "a gate found something":
It is not this PR's doing. Measured across three independent procest PRs tonight with completely different diffs:
Three different diffs, three different sizes, identical outcome. gate-24 ( I deliberately did not reach for What unblocks this: the gate-24 parity work that is in flight ( Not merging on a red Hydra Gates, and not adding a waiver or flipping |
Quality Report — ConductionNL/procest @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue3-compile | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 550/550 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-05 21:43 UTC
Download the full PDF report from the workflow artifacts.
Part of the fleet PHPMD ruleset fix (ConductionNL/.github#155). Propagates the shape
already merged in
nextcloud-app-template#125,doriath#157,larpingapp#263 andplanix#315.The defect
phpmd.xmldeclared:A nested
<exclude-pattern>is inert. PHPMD 2.15 reads exclude-patterns inRuleSetFactory::getIgnorePattern(), which walks$xml->children()— only elementsdirectly under
<ruleset>. A nested one parses without error and is discarded, solib/Migrationwas scanned by the very rule the pattern was written to spare.Reproduced directly on this repo's ruleset: a probe class in
lib/Migrationwith threeunused formal parameters was reported, exit 2, with the nested pattern in place.
Why not simply hoist the pattern
A top-level
<exclude-pattern>is applied by PDepend'sExcludePathFilteratfile-collection time, so it drops the file from every rule in the ruleset — real
complexity,
StaticAccessand method-length findings in migrations would silently vanish.The shape shipped here
phpmd.xmlno longer declaresUnusedFormalParameter; a comment records why.phpmd-unusedparams.xmlholds that rule alone, with a top-level*/Migration/*exclude — so the exclusion is scoped to that one rule and nothing else.phpmdcomposer script runs both legs, keeping the worst exit code so neitherleg can short-circuit the other.
Why
lib/Migrationis exempt from this one rule:OCP\Migration\IMigrationStepmandateschangeSchema(IOutput $output, Closure $schemaClosure, array $options)andpreSchemaChange/postSchemaChangewith the same three parameters. A step that needs noneof them still cannot drop them — the signature is not ours to change.
Measurement
PHPMD 2.15.0 (the version this repo's lockfile pins) on PHP 8.4.22, run in a
nextcloud:latestcontainer: host PHP 8.2 makesvendor/bin/phpmddie inplatform_check.phpwith exit 255, which reads exactly like a clean run. Exit codesare read directly, never through a pipe. Findings are compared as normalised
path:line:ruletriples — PHPMD right-pads thefile:linecolumn in text output, so rawline diffs are meaningless.
@SuppressWarningsstripped on a throwaway copy), beforeThe true count strips every
@SuppressWarningsinlib/on a throwaway copy that isnever committed, so the comparison isolates the ruleset change from the suppressions.
Retired: 0
Newly appearing (true): 0 — must be 0, and is.
Newly appearing (reported): 0. Retired (reported): 0.
Zero newly-hidden findings: every triple present before is present after, except the
retired
UnusedFormalParameterhits insidelib/Migrationlisted above.lib/Migrationin this repo: 0 PHP file(s).Dead-gate proof
A new leg that exits 0 on the shipped tree is indistinguishable from a leg that does not
run. Proven otherwise on a throwaway copy (probes removed before committing) — three
probes, run through the shipped two-leg invocation:
lib/Migration/ZzProbeMigration.phpUnusedFormalParameterlib/Migration/ZzProbeMigration.phpElseExpressionlib/ZzProbe/ZzProbe.phpUnusedFormalParameterLeg 2 goes from exit 0 to exit 2 under the probe, so it is live. The Migration
ElseExpressionprobe is still reported, so isolating the rule did not blind theother rules to
lib/Migration— which is exactly what a hoisted top-level pattern wouldhave done.
The rig itself was positive-controlled before the first measurement: the same probes under
the unfixed ruleset were reported with exit 2, including the three
UnusedFormalParameterhits in
lib/Migrationthat the nested pattern was supposed to suppress.Baseline
phpmd.baseline.xmlpresent: no. None was added, deleted or shrunk.PHPMD auto-discovers
phpmd.baseline.xmlfrom the working directory, so removing the--baseline-fileflag would be a no-op — the baseline stays active either way. Verifiedempirically on this fleet: identical command, baseline file present → 0 findings / exit 0;
same command with the file absent → 93 findings / exit 2.
Suppressions
0 deleted. This repo has no
lib/Migrationdirectory at all, so it carried no@SuppressWarnings(PHPMD.UnusedFormalParameter)tags for this fix to make redundant.This fix therefore retires nothing in this repo. Its value is the corrected shape — the old nested pattern was inert either way, and the new leg is proven live below.
This repo still carries 59
UnusedFormalParameterand 211 other suppressions elsewhere inlib/; those are outside this PR's scope and untouched.Note:
lib/Service/TenantMigrationService.phpcontains "Migration" in its filename. It is not matched by*/lib/Migration/*(nor by*/Migration/*, which needs a directory segment), but it would have been matched by the older*Migration*form. It stays fully analysed.No
@SuppressWarningswas added. No threshold was changed, no rule weakened, no baselineentry added, nothing skipped.
Tests
phpunit -c phpunit-unit.xml:(The default
phpunit.xmlsuite needs a live Nextcloud runtime and errors out in a barecheckout — a pre-existing condition this change does not touch.)
What this PR does NOT do
@SuppressWarnings, a threshold change or a waiverUnusedFormalParameter