Tests / Build Scripts: Configure PHPStan level 0#10419
Tests / Build Scripts: Configure PHPStan level 0#10419justlevine wants to merge 13 commits intoWordPress:trunkfrom
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
86c9442 to
0308f9e
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces PHPStan level 0 static analysis configuration to WordPress Core, establishing a foundation for catching code errors without execution. The implementation includes PHPStan configuration files, bootstrap scripts, documentation, GitHub workflows for CI integration, and inline code annotations to suppress legitimate PHPStan warnings.
Changes:
- Added PHPStan level 0 configuration with baseline support for legacy code
- Added PHPStan annotations to source files to document legitimate suppressions
- Integrated PHPStan into CI/CD via GitHub workflows and npm/composer scripts
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| phpstan.neon.dist | Main PHPStan configuration with level 0 rules and baseline inclusion |
| tests/phpstan/base.neon | Base configuration defining paths, exclusions, and codebase-specific settings |
| tests/phpstan/baseline.php | Empty baseline file for future tech debt tracking |
| tests/phpstan/bootstrap.php | Defines WordPress constants for PHPStan discovery |
| tests/phpstan/README.md | Documentation for running and configuring PHPStan |
| src/wp-includes/template.php | Added PHPStan annotations and removed obsolete comment |
| src/wp-includes/style-engine/class-wp-style-engine-css-rules-store.php | Added @phpstan-consistent-constructor annotation |
| src/wp-includes/media.php | Added annotation for PHP8+ GdImage class |
| src/wp-includes/functions.php | Added enhanced return type documentation for wp_die() |
| src/wp-includes/customize/*.php | Added @return documentation for overridden update() methods |
| src/wp-includes/class-wp-theme-json.php | Added null return value to match documented return type |
| src/wp-includes/class-wp-scripts.php | Removed obsolete PHPStan suppression comment |
| src/wp-admin/press-this.php | Added PHPStan annotations for plugin file includes |
| src/wp-admin/includes/class-wp-filesystem-ssh2.php | Added phpstan-ignore-next-line for unimplemented method |
| composer.json | Added phpstan/phpstan dependency and analyse script |
| package.json | Added test:php:stan npm script |
| phpcs.xml.dist | Excluded PHPStan files from coding standards checks |
| .gitignore | Added phpstan.neon to ignore list for local overrides |
| .github/workflows/php-static-analysis.yml | Main workflow for running PHPStan on pushes and PRs |
| .github/workflows/reusable-php-static-analysis.yml | Reusable workflow for PHPStan execution |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tests/phpstan/README.md
Outdated
| composer run analyse | ||
|
|
||
| compose run analyse -- --memory-limit=4G | ||
| compose run analyse -- tests/phpstan/src/wp-includes/template.php |
There was a problem hiding this comment.
Incorrect path in example. The path should be "src/wp-includes/template.php" not "tests/phpstan/src/wp-includes/template.php" since PHPStan analyses files in the src directory, not in tests/phpstan/src
| "squizlabs/php_codesniffer": "3.13.5", | ||
| "wp-coding-standards/wpcs": "~3.3.0", | ||
| "phpcompatibility/phpcompatibility-wp": "~2.1.3", | ||
| "phpstan/phpstan": "~2.1.33", |
Copilot
AI
Feb 12, 2026
•
There was a problem hiding this comment.
The version constraint "~2.1.33" is very specific and may cause issues. The tilde operator (~) for a three-part version like 2.1.33 means ">=2.1.33 <2.2.0". This constraint locks to a specific patch version which may not exist or may prevent receiving important bug fixes. Consider using "^2.1" (which means ">=2.1.0 <3.0.0") or "~2.1.0" (which means ">=2.1.0 <2.2.0") instead to allow flexibility for patch updates while staying within the same minor version
| "phpstan/phpstan": "~2.1.33", | |
| "phpstan/phpstan": "~2.1.0", |
There was a problem hiding this comment.
@westonruter what are your thoughts on this one?
I definitely think we should pin at the version we commit, but I wouldn't want to Semver because contextually "nonbreaking enhancements" are breaking from an implementation POV if they create a new quality gate.
There was a problem hiding this comment.
That makes sense to me. So switching to ~2.1.0 would keep it at 2.1.x. This seems necessary because there is no composer.lock, which actually is curious since we package-lock.json. If we had a composer.lock then we'd be free to use ^2.1. But I suppose can't use it because of the different versions of PHP which may end up getting used when doing composer install.
So yeah, I guess go with ~2.1.0 and not ^2.1. When PHPStan 2.2 comes out, we'll have to manually upgrade.
| name: PHPStan Static Analysis | ||
|
|
||
| on: | ||
| # PHPStan testing was introduced in @todo. |
There was a problem hiding this comment.
The comment contains a TODO placeholder "@todo" that should be replaced with the actual version number when PHPStan testing is introduced
| # PHPStan testing was introduced in @todo. | |
| # Run PHPStan testing on pushes to the following branches and tags. |
| npm run test:php:stan -- --memory-limit=4G | ||
|
|
||
| # to analyse only a specific file: | ||
| npm run test:php:stan -- tests/phpstan/src/wp-includes/template.php |
There was a problem hiding this comment.
Incorrect path in example. The path should be "src/wp-includes/template.php" not "tests/phpstan/src/wp-includes/template.php" since PHPStan analyses files in the src directory, not in tests/phpstan/src
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| * @since 3.4.0 | ||
| * | ||
| * @param mixed $value The value to update. Not used. | ||
| * @return bool|void Nothing is returned. |
There was a problem hiding this comment.
| * @return bool|void Nothing is returned. | |
| * @return bool Always returns true. |
| * @return bool|void Nothing is returned. | ||
| */ | ||
| public function update( $value ) { | ||
| remove_theme_mod( 'background_image_thumb' ); |
There was a problem hiding this comment.
| remove_theme_mod( 'background_image_thumb' ); | |
| remove_theme_mod( 'background_image_thumb' ); | |
| return true; |
There was a problem hiding this comment.
Even though I agree this is the real fix, I'm nervous about making any src code changes in a tooling PR. Am I overthinking?
There was a problem hiding this comment.
I can commit these fixes separately if you like.
| * @since 3.4.0 | ||
| * | ||
| * @param mixed $value The value to update. | ||
| * @return bool|void Nothing is returned. |
There was a problem hiding this comment.
| * @return bool|void Nothing is returned. | |
| * @return bool Always returns true. |
| * @param mixed $value The value to update. | ||
| * @return bool|void Nothing is returned. | ||
| */ | ||
| public function update( $value ) {} |
There was a problem hiding this comment.
| public function update( $value ) {} | |
| public function update( $value ) { | |
| return true; | |
| } |
| * @global Custom_Image_Header $custom_image_header | ||
| * | ||
| * @param mixed $value The value to update. | ||
| * @return bool|void Nothing is returned. |
There was a problem hiding this comment.
| * @return bool|void Nothing is returned. | |
| * @return bool Always returns true. |
| } else { | ||
| $custom_image_header->set_header_image( $value ); | ||
| } | ||
| } |
There was a problem hiding this comment.
| return true; | |
| } |
| "lock": false | ||
| }, | ||
| "scripts": { | ||
| "analyse": "@php ./vendor/bin/phpstan analyse --memory-limit=2G", |
There was a problem hiding this comment.
😛
| "analyse": "@php ./vendor/bin/phpstan analyse --memory-limit=2G", | |
| "analyze": "@php ./vendor/bin/phpstan analyse --memory-limit=2G", |
There was a problem hiding this comment.
But I wonder, why not just:
| "analyse": "@php ./vendor/bin/phpstan analyse --memory-limit=2G", | |
| "phpstan": "@php ./vendor/bin/phpstan analyse --memory-limit=2G", |
There was a problem hiding this comment.
This would be closer to test:php:stan.
There was a problem hiding this comment.
I'm torn. I prefer the US spelling, but the name of the PHPStan command is analyse and the muscle memory of folks who use PHPStan. 2 years ago this would have been a nit, but it also affects LLMs since the inconsistency can get in the way of their inference
There was a problem hiding this comment.
What about just phpstan then?
There was a problem hiding this comment.
There is a compat script which isn't a verb already.
| "env:logs": "node ./tools/local-env/scripts/docker.js logs", | ||
| "env:pull": "node ./tools/local-env/scripts/docker.js pull", | ||
| "test:performance": "wp-scripts test-playwright --config tests/performance/playwright.config.js", | ||
| "test:php:stan": "node ./tools/local-env/scripts/docker.js run --rm php ./vendor/bin/phpstan analyse --memory-limit=2G", |
There was a problem hiding this comment.
I wonder about this one. There isn't an npm script for PHPCS. Is that an oversight? It's in a grunt task instead: npx grunt lint:php. It seems like there should be something exposed for PHPCS if something is added for PHPStan.
That said, I'm not sure about test:php:stan. It's not actually running tests. It's analyzing. And "stan" isn't a thing, as I understand it is "STatic+ANalysis". I'd feel better about something like static-analysis:phpstan.
Relatedly: I want to add TypeScript tsc here as well, so that could be static-analysis:js which just runs tsc.
There was a problem hiding this comment.
I'm good with whatever here 🤷.
I prefixed it with test because that's matches the files and to be pedantic we're testing type safety, also linting is also a type of "static analysis". FWIW downstream I usually do npm run lint:php:stan (or even npm run lint:php:types to match a lint:js:types ), which sits nicely next to npm run lint:php and npm run lint:js, and works well with autocomplete and progressive disclosure.
And "stan" isn't a thing,
Agreed, the motivation here is just that {lint|test}:php:phpstan is redundantly ugly.
|
In |
@westonruter can you run it with (Also confirming that you're using the vanilla |
|
Actually, I was using a local wordpress-develop/tests/phpstan/base.neon Lines 62 to 63 in 75c8c54 If I change it to: paths:
- ../../src/wp-admin
- ../../src/wp-includesThen it works, aside from two errors being found: What appears to be the issue is that I use my Nevertheless, the change I made is not ideal because then it doesn't check the PHP files in the root of |


Trac ticket: https://core.trac.wordpress.org/ticket/61175
This PR adds a PHPStan configuration for PHPStan level 0, along with tests and docs.
Based from #7619 - which remains in use to explore adopting future levels (alongside parallel remediation branches).
Proposal: https://make.wordpress.org/core/2025/07/11/proposal-phpstan-in-the-wordpress-core-development-workflow/
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.