Skip to content

Build/Test Tools: Update PHPStan to 2.3.5 and document --fix - #15479

Open
westonruter wants to merge 5 commits into
WordPress:trunkfrom
westonruter:update/phpstan-2.3.5
Open

westonruter wants to merge 5 commits into
WordPress:trunkfrom
westonruter:update/phpstan-2.3.5

Conversation

@westonruter

@westonruter westonruter commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Updates PHPStan from 2.3.4 to 2.3.5, and adds composer phpstan:fix (and npm run typecheck:php:fix), which uses its new --fix option to update the baselines after fixing a baselined error.

The phpstan-phpunit and phpstan-strict-rules extensions are already at their latest versions (2.1.1 each). With 2.3.5, the analysis of phpstan.neon.dist reports no errors and no stale baseline entries, so the baselines are unchanged.

--fix for stale ignores

PHPStan 2.3.5 marks errors it can fix itself with 🔧, and these include stale ignores. With --fix, it lowers a baseline entry's count that is now too high, removes an entry that matches nothing, and removes an @phpstan-ignore comment that no longer ignores anything. That is quicker than composer phpstan:baselines, since it reuses the result cache. Unlike regenerating, it also clears stale inline ignores, which the CI messages previously said had to be removed by hand.

Verified against the per-identifier baselines:

  • An entry's count inflated from 1 to 3 was lowered back to 1, leaving the rest of the file untouched.
  • Removing the last entry of a baseline leaves that file in place as ignoreErrors: [], still listed in the includes of phpstan.neon.dist.
  • A run limited to some paths reports no stale ignores at all, so --fix has to be run on the whole tree.
  • With --fix, PHPStan reports only what it fixed, and exits with 0 when every fixable error was fixed, even when other errors remain. It exits with 1 when nothing was fixable.
  • Run with a local phpstan.neon at level 10 with bleeding edge, --fix removed entries from five baselines that phpstan.neon.dist still needs, because that configuration reports those errors with different messages.

composer phpstan:fix and --prune

composer phpstan:fix chains three steps, each run only when the one before it succeeds:

  1. phpstan analyse --configuration=phpstan.neon.dist --fix, which updates the baselines in place.
  2. composer phpstan:baselines -- --prune, a new option of the baseline generator which analyzes nothing. It deletes each baseline that lists no entries, and rewrites the list between the # phpstan:baselines markers to match. It respects --identifier and --output-dir.
  3. phpstan analyse --configuration=phpstan.neon.dist, since --fix reports only what it fixed. It reuses the result cache, so it is quick, and it is what makes the script fail when errors remain.

Both analyses name phpstan.neon.dist, whatever a local phpstan.neon says, given the last finding above. The scripts also avoid the -- that composer phpstan -- --fix needs, which Composer otherwise discards silently along with the flag. When nothing is fixable, PHPStan's exit status of 1 stops the chain after the first step, which the README notes.

Changes

  • tests/phpstan/README.md gains a section, "Removing stale entries with --fix", covering the above, the fact that --fix applies every fixable error the run reports, and --fix-ignored.
  • tests/phpstan/generate-baselines.php gains the --prune option.
  • The CI step summary, the ::error annotation, the annotation of fixable errors, and the pull request comment now point to composer phpstan:fix (or npm run typecheck:php:fix), with regenerating as the alternative.
  • Annotations of errors that PHPStan's JSON output flags as fixable say how to fix them. The flag is left out of the results passed to the comment workflow, as tips are.
  • A workflow comment is corrected: since 2.3.5, an unmatched ignoreErrors entry without a path is reported against the config file it is in, rather than as an error not specific to a file.

Not adopted

  • Registering the extensions with attributes rather than in base.neon: this only works when PHPStan runs on PHP 8.0+, and core's tooling still supports 7.4. The comments documenting each service in base.neon are also worth keeping.
  • Making the hook rules auto-fixable: each of their errors needs a decision from a person (writing the documentation, or choosing which argument count is right), which PHPStan's release notes say is not a good fit for --fix.

Trac ticket: Core-65817

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5 (medium effort)
Used for: Researching the 2.3.5 changes, testing --fix against the baselines, and writing the version bump, documentation, and workflow changes, all reviewed by me.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

westonruter and others added 2 commits November 4, 2026 14:30
The analysis of `phpstan.neon.dist` reports no errors with this release,
and no baseline entry goes stale, so the baselines are unchanged. The
phpstan-phpunit and phpstan-strict-rules extensions are already at their
latest versions (2.1.1 each).

The headline feature of 2.3.5 is `--fix`, which also removes stale
`ignoreErrors` entries and `@phpstan-ignore` comments. Its adoption in
the docs and CI follows separately.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tnky3GdCg3QL5KeMvuQvwf
Claude-Code-Session-Id: 5bac4eea-faaa-40e6-8440-b696b7773ba6
PHPStan 2.3.5 can fix stale ignores itself: `--fix` lowers a baseline
entry's count that is now too high, removes an entry that matches
nothing, and removes an `@phpstan-ignore` comment that no longer ignores
anything. That is quicker than regenerating the baselines, since it
reuses the result cache, and unlike regenerating it also clears stale
inline ignores, which before had to be removed by hand.

Verified against the per-identifier baselines: an inflated count was
lowered back to its real value, and removing the last entry of a
baseline left that file in place as `ignoreErrors: []`. Regenerating
that one identifier then deleted the file and its `includes` entry. A
run limited to some paths reports no stale ignores at all, so `--fix`
has to be run on the whole tree.

The README gains a section describing this, its caveats, and
`--fix-ignored`. The CI step summary, the error annotation, and the pull
request comment now suggest `--fix` first, with regenerating as the
alternative that also retires an emptied baseline.

Annotations of errors that PHPStan reports as fixable, which its JSON
output now flags, also say how to fix them. That flag is left out of the
results passed to the comment workflow, as tips are.

A comment about errors not specific to a file is corrected too: since
2.3.5, an unmatched `ignoreErrors` entry without a path is reported
against the config file it is in.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tnky3GdCg3QL5KeMvuQvwf
Claude-Code-Session-Id: 5bac4eea-faaa-40e6-8440-b696b7773ba6
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

westonruter and others added 3 commits November 7, 2026 19:22
…rator.

Running PHPStan with `--fix` removes the entries of fixed errors from a
baseline in place, but when it removes the last one it leaves the file
behind as `ignoreErrors: []`, with its `includes` entry in
`phpstan.neon.dist`. Only regenerating that identifier deleted it, which
reruns the whole analysis just to find that nothing is left.

`composer phpstan:baselines -- --prune` analyzes nothing. It deletes each
per-identifier baseline that lists no entries, whether as an empty
inline list or a key with nothing nested under it, and rewrites the list
between the `# phpstan:baselines` markers to match. With `--identifier`,
only those baselines are considered. A file without an `ignoreErrors`
key is left alone with a warning, and `--combined` is rejected, since it
writes nothing.

Only whole-line comments are skipped when reading a baseline, since a
message pattern such as `'#^…$#'` contains `#`.

`read_file()` now takes a `non-empty-string` path, as returned by
`find_baselines()`, since nothing in it needs the path to be non-falsy.

The README and the CI step summary now say to prune after `--fix`
empties a baseline, rather than to regenerate it.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tnky3GdCg3QL5KeMvuQvwf
Claude-Code-Session-Id: 5bac4eea-faaa-40e6-8440-b696b7773ba6
…selines.

Fixing a baselined error meant running `composer phpstan -- --fix`,
then `composer phpstan:baselines -- --prune` for any baseline it
emptied. Leaving out the `--` makes Composer discard the flag silently,
so the analysis just reports the stale entries again.

`composer phpstan:fix` (and `npm run typecheck:php:fix`) chains three
steps, each run only when the one before it succeeds:

* PHPStan with `--fix`, which lowers or removes the stale entries.
* `composer phpstan:baselines -- --prune`, which deletes any baseline
  left with no entries, along with its `includes` entry.
* The analysis once more, since PHPStan's `--fix` reports only what it
  fixed, and succeeds even when other errors remain. It reuses the
  result cache, so it is quick.

Both analyses use `phpstan.neon.dist`, whatever a local `phpstan.neon`
says. A configuration at another rule level can report a baselined error
with a different message, and `--fix` then removes entries the default
configuration still needs. That happened when this was tried against a
local level 10 configuration with bleeding edge, which removed entries
from five baselines.

When nothing is fixable, PHPStan exits with 1 and stops the chain, which
the README notes.

The README, the CI step summary, the annotation of fixable errors, and
the pull request comment now point to the new scripts.

Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tnky3GdCg3QL5KeMvuQvwf
Claude-Code-Session-Id: 5bac4eea-faaa-40e6-8440-b696b7773ba6
@westonruter
westonruter marked this pull request as ready for review November 8, 2026 06:44
@westonruter

Copy link
Copy Markdown
Member Author

cc @apermo

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

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