Skip to content

Add Psalm static analysis (composer analyse + CI job)#3

Merged
Hermsi1337 merged 5 commits into
mainfrom
chore/psalm-static-analysis
May 26, 2026
Merged

Add Psalm static analysis (composer analyse + CI job)#3
Hermsi1337 merged 5 commits into
mainfrom
chore/psalm-static-analysis

Conversation

@Hermsi1337
Copy link
Copy Markdown
Member

Summary

Wires Psalm in as a static-analysis safety net for the production code and the test suite. Runs locally via `composer analyse` and in CI as a new parallel job alongside the unit-test matrix.

Configuration

  • errorLevel="6" — a middle-ground bar that catches real bugs without drowning in `MixedReturnStatement` noise from Roundcube interop. Future PRs are held to this bar.
  • projectFiles: `imapsync.php` + `lib/` + `tests/` (both unit and integration).
  • extraFiles: the `rcube_imap_generic` / `rcube_plugin` / `rcube_utils` / `rcmail` / `html_*` classes under `dist/roundcubemail-1.7.0/program/lib/Roundcube/` so Psalm can resolve symbols without analysing them.
  • Small baseline (`psalm-baseline.xml`) covers three brownfield type-shape complaints:
    • `imapsync.php` — a `NullArgument` site
    • `lib/RoundcubeImapSyncClient.php` — the `bool` return type of `appendMessage` vs `rcube_imap_generic::append()`'s actual `bool|int|false` shape
    • `tests/Fakes/FakeImapClient.php` — same shape issue in the `nextUid` helper

All three are Roundcube-interop quirks, not real bugs. They get baselined now and can be tightened later. With the baseline in place: "No errors found!", 97% inferred type coverage.

CI

  • New `static-analysis` job runs in parallel to `unit-tests` (no `needs:` chain) so type feedback arrives without waiting for the PHP matrix.
  • `composer dist:fetch` runs first so Roundcube symbols resolve.

Docs

  • `AGENTS.md` layout block + Testing subsection + freshness paragraph reference the new check.
  • `README.md` dev-commands list adds `composer analyse`.

Breaking changes

None. Pure dev-tooling addition.

How to verify

```bash
composer install
composer dist:fetch
composer analyse
```

Expected: `No errors found!` after applying the baseline.

🤖 Generated with Claude Code

Hermsi1337 and others added 5 commits May 26, 2026 15:36
Wires Psalm in as a static-analysis safety net for the production code
and the test suite. Runs locally via `composer analyse` and in CI as
a new parallel job alongside the unit-test matrix.

Configuration:
- errorLevel="6", a middle-ground bar that catches real bugs without
  drowning in MixedReturnStatement noise from Roundcube interop.
- projectFiles: imapsync.php + lib/ + tests/. extraFiles: the
  rcube_imap_generic / rcube_plugin / rcube_utils / rcmail / html-*
  classes under dist/roundcubemail-1.7.0/program/lib/Roundcube/ so
  Psalm can resolve symbols without analysing them.
- A small baseline (psalm-baseline.xml) covers three brownfield
  type-shape complaints (NullArgument in imapsync.php, the
  bool-vs-int return mismatch where we hand back rcube_imap_generic's
  append() result, and the same kind of issue in FakeImapClient's
  nextUid helper). All three are Roundcube-interop quirks, not real
  bugs — they get baselined now and can be tightened later. With the
  baseline in place: "No errors found!", 97% inferred type coverage.

CI:
- New static-analysis job runs in parallel to unit-tests (no needs:)
  so type feedback arrives without waiting for the PHP matrix.
- composer dist:fetch runs first so Roundcube symbols resolve.

Docs:
- AGENTS.md layout block + Testing subsection + freshness paragraph
  reference the new check.
- README dev-commands list adds composer analyse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI revealed that Psalm 5.x caps PHP support at 8.3 (require:
"^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0"), which made
composer install fail on the PHP 8.4 unit-test matrix job before any
test could run.

Psalm 6 supports PHP 8.1-8.4 cleanly. Bump the dev dep to ^6.0 and
drop the Docker-fallback hack from the analyse script (it was working
around exactly this incompatibility but only at run time, not at
install time, so it didn't help the matrix anyway).

Regenerate psalm-baseline.xml against Psalm 6 — the new strict checks
(MissingOverrideAttribute, ClassMustBeFinal, additional InvalidReturnType
shapes) add ~25 brownfield entries to the baseline. With the rebuild
in place: "No errors found!", 96% type inference. Unit tests still
green locally (24/24).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the unit-test matrix (8.1/8.2/8.3/8.4 → 8.4 only) and bumps
the integration-tests and static-analysis jobs from 8.3 to 8.4.

Roundcube's current standard target is 8.4. Testing the older
versions in CI gave us coverage we never used in production. The
composer.json still declares ">=8.1" so installs on older PHP
versions remain unblocked, but CI no longer validates them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 67-line psalm-baseline.xml from the Psalm 6 bump was a known-debt
file: it covered real issues that we were just suppressing. This
commit fixes them all and removes the baseline so future regressions
fail the build cleanly.

Fixes:

- `#[\Override]` attribute added to every method that overrides a
  parent or implements an interface method (init, the 12 methods on
  RoundcubeImapSyncGenericClient and FakeImapClient that implement
  the RoundcubeImapSyncClient interface, the four PHPUnit lifecycle
  hooks setUpBeforeClass / tearDownAfterClass on the two integration
  test classes). Auto-applied via `psalm --alter`.

- `imapsync.php`: addFormRow passed `null` as the first arg to
  `html_table::add`, which Psalm reads as "no column attributes" but
  the signature types as `array|string`. Use `''` to mean the same
  thing without the null-into-non-nullable shape mismatch.

- `lib/RoundcubeImapSyncClient.php::appendMessage`: cast the return
  to `(bool)`. `rcube_imap_generic::append()` returns mixed: false on
  failure, true on plain success, or the appended UID (string) when
  the server supports UIDPLUS. Our interface only contracts a bool,
  so collapse the success cases.

- `tests/Fakes/FakeImapClient.php::nextUid`: narrow the inner
  message-array type via a local `@var non-empty-array<int, mixed>`
  annotation so `max(array_keys(...))` infers as `int` instead of
  `float|int`.

`psalm-baseline.xml` and the `errorBaseline` attribute in
`psalm.xml` are gone. `composer analyse` now returns "No errors
found!" against the raw codebase. 24 unit + 7 integration tests
still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Followup to the baseline removal. The layout block still listed it
as a tracked file. Updated and made the no-baseline policy visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Hermsi1337 Hermsi1337 merged commit 720ddad into main May 26, 2026
4 checks passed
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