Skip to content

chore(quality): burn PHPMD to a true zero and delete phpmd.baseline.xml - #217

Open
rubenvdlinde wants to merge 2 commits into
developmentfrom
chore/phpmd-baseline-burndown
Open

chore(quality): burn PHPMD to a true zero and delete phpmd.baseline.xml#217
rubenvdlinde wants to merge 2 commits into
developmentfrom
chore/phpmd-baseline-burndown

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

PHPMD: reported 0, actually 5 — now genuinely 0, baseline deleted

Part of the fleet suppression audit (ConductionNL/.github#155). Audit issue: #215.

phpmd.baseline.xml made composer phpmd — and the "PHP Quality (phpmd)" CI job — report a green zero over five live findings. This PR deletes the baseline rather than regenerating it smaller, and burns the findings it was hiding down to a true zero.

Numbers

Measured with PHPMD 2.15.0 / PHP 8.4.22, exit code read directly (0 clean, 2 violations, 1/255 tool failure):

findings exit
reported today (baseline present) 0 0
baseline deleted, before this PR 5 2
baseline deleted, after this PR 0 0

Positive-controlled: a deliberate violation added to a throwaway copy is reported and exits 2, so the zero above is a measured zero and not a tool that failed to start.

Worth noting: the baseline had 9 entries hiding only 5 live findings. The four CustomCssValidator::validate / ShortVariable / UnusedLocalVariable entries had gone stale against the #193 fix. Entry counts are not finding counts.

Triage of the 5

All five were genuine debt — no interface-mandated signature and nothing structurally unreachable. So the baseline is deletable outright.

finding rule fix
CustomCssValidator::bracesAreBalanced() CyclomaticComplexity 17, NPath 961 split the state machine
CssInjectionService::inject() CyclomaticComplexity 10, NPath 288 split the cascade
CssInjectionService (class) CouplingBetweenObjects 13 extract the preview banner

What changed

CustomCssValidator::bracesAreBalanced() — the hand-rolled scanner becomes structuralBraces(), which consumes comments and quoted strings whole (skipComment() / skipQuoted()) and returns only the braces that carry structural meaning, plus a four-line depth counter. Escapes, unterminated comments and unterminated strings are handled exactly as before.

CssInjectionService::inject() — split into injectDesignSystemStyles(), injectOverrideStyles(), injectCustomFontLink() and injectConditionalStyles(). Emission order, short-circuiting and every gate condition are unchanged; the two inverted conditions are exact De Morgan equivalents. The existing cascade-order tests assert the full emitStyle() sequence and are untouched apart from the constructor arity.

ThemePreviewBannerService (new) — the preview banner leaves CssInjectionService, taking ThemePreviewService, IUserSession, IInitialState and \Throwable with it. The stylesheet cascade needed none of them. Coupling 13 → 10, constructor 10 params → 8, which retires the now-redundant @SuppressWarnings(PHPMD.ExcessiveParameterList).

phpmd.xml — the nested <exclude-pattern>*Migration*</exclude-pattern> under UnusedFormalParameter is removed. PHPMD honours <exclude-pattern> only as a direct child of <ruleset>; nested inside a <rule> it is parsed and discarded, so it never excluded anything. It is deliberately not reinstated at ruleset level: lib/Migration produces zero UnusedFormalParameter findings, so making it live would add a suppression rather than remove one. A pointer comment records the correct shape (a dedicated ruleset with a top-level pattern) should a mandated-but-unused IMigrationStep signature ever land here.

composer.json--baseline-file phpmd.baseline.xml dropped. Note this alone would have changed nothing: PHPMD auto-discovers phpmd.baseline.xml from the working directory, so deleting the file is what actually disables it.

Tests

before after
tests 556 560
assertions 4261 4273

All passing. The four new tests cover ThemePreviewBannerService — logic that previously had no coverage at all, because CssInjectionServiceTest stubbed injectPreviewBanner() out.

phpcs 0, psalm 0, phpstan 0.

What I did NOT do

  • did not add a @SuppressWarnings, a baseline entry, a threshold change, a .skip or a weakened assertion
  • did not regenerate a smaller baseline
  • did not touch any rule threshold in phpmd.xml
  • did not change behaviour anywhere — no stylesheet, no cascade position, no gate condition, no validator verdict

Pre-existing CI state

development is already red at 9a8877a: Frontend Check (test:l10n), Integration Tests (Newman), Hydra Gates and the Quality Report aggregate all fail there. Those are not caused by this PR. The PHPMD job passes on development today (over the baseline) and passes here on a true zero.

`phpmd.baseline.xml` made `composer phpmd` report a green zero over five live
findings. It is deleted, not regenerated smaller: with the file gone PHPMD now
reports 0 findings and exits 0 on lib/, verified with a positive control (a
deliberate violation in a throwaway copy is reported and exits 2).

Measured with PHPMD 2.15.0 on PHP 8.4.22:

  reported (baseline present)  0
  baseline deleted, before     5
  baseline deleted, after      0

The 9 baseline entries were only hiding 5 live findings — the four
CustomCssValidator::validate / ShortVariable / UnusedLocalVariable entries had
already gone stale against the #193 fix. Entry counts are not finding counts.

What was fixed, all behaviour-preserving:

* CustomCssValidator::bracesAreBalanced() — CyclomaticComplexity 17, NPath 961.
  The single hand-rolled state machine is split into structuralBraces() (which
  consumes comments and quoted strings whole via skipComment()/skipQuoted() and
  returns only the structural braces) and a four-line depth counter. Same
  treatment of escapes, unterminated comments and unterminated strings.

* CssInjectionService::inject() — CyclomaticComplexity 10, NPath 288.
  The cascade is split into injectDesignSystemStyles(), injectOverrideStyles(),
  injectCustomFontLink() and injectConditionalStyles(). Emission order,
  short-circuiting and every gate condition are unchanged; the two inverted
  conditions are exact De Morgan equivalents of the originals.

* CssInjectionService — CouplingBetweenObjects 13.
  The theme-preview banner moves to a new ThemePreviewBannerService, taking
  ThemePreviewService, IUserSession, IInitialState and \Throwable with it. The
  stylesheet cascade needed none of them. Coupling drops to 10 and the
  constructor from 10 parameters to 8, which retires the now-redundant
  @SuppressWarnings(PHPMD.ExcessiveParameterList).

* phpmd.xml — the nested `<exclude-pattern>*Migration*</exclude-pattern>` under
  UnusedFormalParameter is removed. PHPMD honours exclude-patterns only as a
  direct child of `<ruleset>`, so it never excluded anything. It is NOT
  reinstated at ruleset level: lib/Migration produces zero UnusedFormalParameter
  findings, so making it live would add a suppression rather than remove one.

* composer.json — `--baseline-file phpmd.baseline.xml` dropped from the script.
  (PHPMD also auto-discovers the file, so deleting it is what actually matters.)

No @SuppressWarnings, baseline entry, threshold change or weakened assertion was
added anywhere.

Tests: 556 -> 560 (4261 -> 4273 assertions), all passing. The four new tests
cover ThemePreviewBannerService, whose logic previously had no coverage at all —
CssInjectionServiceTest stubbed injectPreviewBanner() out.

phpcs 0, psalm 0, phpstan 0.

Refs #215, ConductionNL/.github#155
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/nldesign @ 9021f7c

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
composer ✅ 100/100
npm ✅ 2/2
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-05 12:52 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/nldesign @ 867ecdc

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
composer ✅ 100/100
npm ✅ 2/2
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-05 20:14 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Held — blocked by gate-46 on pre-existing @spec debt in the files this PR touches.

Hydra Gates on the PR head is a real, scoped run:

[hydra-gates] Scope: diff vs origin/development — N changed file(s)
[gate-46] spec-anchor-existence: FAIL — unresolved @spec target(s)

The gates are diff-scoped per ADR-020, so touching a file legitimately pulls its existing dangling @spec anchors into scope. That is the intended "you touched it, you own it" behaviour, not a gate malfunction — and it means no base run can excuse it, since the finding is scoped to files only this PR touched.

I also want to be explicit about what I did not do: I did not cite development as "also red". A push to the base branch scopes 0 files and Hydra Gates passes in ~20s having inspected nothing. That is a vacuous green and proves nothing — only a genuinely red base is evidence.

What this needs: repoint the unresolved anchors onto headings that actually exist. The technique is straightforward and worth doing properly — see openregister#2355 for a worked example, including the check that matters:

resolve each @spec anchor against the spec's own headings, stripping the
"Requirement: " prefix, AND include a negative control — the OLD anchor must
come back MISS. Without that control a green result only tells you the
checker is permissive.

That control is the whole point: it is what distinguishes "the anchors resolve" from "my slugifier matches everything". When I first ran it against openregister I got 5 MISS out of 5 — the anchors were fine and my checker was wrong (it was not stripping the Requirement: prefix).

Not adding a waiver, a baseline entry, or a require-full-coverage flip to get past this — each of those converts a gate that is inconvenient once into a gate that is silent permanently.

The underlying quality work in this PR is sound and measured; it is only the anchor debt in the touched files standing between it and a merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant