Skip to content

Commit 86c9442

Browse files
committed
tests: phpstan level 0
1 parent 8dd04fb commit 86c9442

20 files changed

+555
-4
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: PHPStan Static Analysis
2+
3+
on:
4+
# PHPStan testing was introduced in @todo.
5+
push:
6+
branches:
7+
- trunk
8+
- '6.9'
9+
- '[7-9].[0-9]'
10+
tags:
11+
- '6.9'
12+
- '6.9.[0-9]+'
13+
- '[7-9].[0-9]'
14+
- '[7-9]+.[0-9].[0-9]+'
15+
pull_request:
16+
branches:
17+
- trunk
18+
- '6.9'
19+
- '[7-9].[0-9]'
20+
paths:
21+
# This workflow only scans PHP files.
22+
- '**.php'
23+
# These files configure Composer. Changes could affect the outcome.
24+
- 'composer.*'
25+
# These files configure PHPStan. Changes could affect the outcome.
26+
- 'phpstan.neon.dist'
27+
- 'tests/phpstan/base.neon'
28+
# Confirm any changes to relevant workflow files.
29+
- '.github/workflows/php-static-analysis.yml'
30+
- '.github/workflows/reusable-php-static-analysis.yml'
31+
workflow_dispatch:
32+
33+
# Cancels all previous workflow runs for pull requests that have not completed.
34+
concurrency:
35+
# The concurrency group contains the workflow name and the branch name for pull requests
36+
# or the commit hash for any other events.
37+
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
38+
cancel-in-progress: true
39+
40+
# Disable permissions for all available scopes by default.
41+
# Any needed permissions should be configured at the job level.
42+
permissions: {}
43+
44+
jobs:
45+
# Runs PHPStan Static Analysis.
46+
phpstan:
47+
name: PHP coding standards
48+
uses: ./.github/workflows/reusable-php-static-analysis.yml
49+
permissions:
50+
contents: read
51+
if: ${{ github.repository == 'WordPress/wordpress-develop' || ( github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' ) }}
52+
53+
slack-notifications:
54+
name: Slack Notifications
55+
uses: ./.github/workflows/slack-notifications.yml
56+
permissions:
57+
actions: read
58+
contents: read
59+
needs: [ phpstan ]
60+
if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name != 'pull_request' && always() }}
61+
with:
62+
calling_status: ${{ contains( needs.*.result, 'cancelled' ) && 'cancelled' || contains( needs.*.result, 'failure' ) && 'failure' || 'success' }}
63+
secrets:
64+
SLACK_GHA_SUCCESS_WEBHOOK: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }}
65+
SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }}
66+
SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }}
67+
SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }}
68+
69+
failed-workflow:
70+
name: Failed workflow tasks
71+
runs-on: ubuntu-24.04
72+
permissions:
73+
actions: write
74+
needs: [ slack-notifications ]
75+
if: |
76+
always() &&
77+
github.repository == 'WordPress/wordpress-develop' &&
78+
github.event_name != 'pull_request' &&
79+
github.run_attempt < 2 &&
80+
(
81+
contains( needs.*.result, 'cancelled' ) ||
82+
contains( needs.*.result, 'failure' )
83+
)
84+
85+
steps:
86+
- name: Dispatch workflow run
87+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
88+
with:
89+
retries: 2
90+
retry-exempt-status-codes: 418
91+
script: |
92+
github.rest.actions.createWorkflowDispatch({
93+
owner: context.repo.owner,
94+
repo: context.repo.repo,
95+
workflow_id: 'failed-workflow.yml',
96+
ref: 'trunk',
97+
inputs: {
98+
run_id: `${context.runId}`,
99+
}
100+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
##
2+
# A reusable workflow that runs PHP Static Analysis tests.
3+
##
4+
name: PHP Static Analysis
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
php-version:
10+
description: 'The PHP version to use.'
11+
required: false
12+
type: 'string'
13+
default: 'latest'
14+
15+
# Disable permissions for all available scopes by default.
16+
# Any needed permissions should be configured at the job level.
17+
permissions: {}
18+
19+
jobs:
20+
# Runs PHP static analysis tests.
21+
#
22+
# Violations are reported inline with annotations.
23+
#
24+
# Performs the following steps:
25+
# - Checks out the repository.
26+
# - Sets up PHP.
27+
# - Logs debug information.
28+
# - Installs Composer dependencies.
29+
# - Configures caching for PHP static analysis scans.
30+
# - Make Composer packages available globally.
31+
# - Runs PHPStan static analysis (with Pull Request annotations).
32+
# - Saves the PHPStan result cache.
33+
# - Ensures version-controlled files are not modified or deleted.
34+
phpstan:
35+
name: Run PHP static analysis
36+
runs-on: ubuntu-24.04
37+
permissions:
38+
contents: read
39+
timeout-minutes: 20
40+
41+
steps:
42+
- name: Checkout repository
43+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
44+
with:
45+
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
46+
persist-credentials: false
47+
48+
- name: Set up PHP
49+
uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
50+
with:
51+
php-version: ${{ inputs.php-version }}
52+
coverage: none
53+
tools: cs2pr
54+
55+
- name: Log debug information
56+
run: |
57+
composer --version
58+
59+
# This date is used to ensure that the Composer cache is cleared at least once every week.
60+
# http://man7.org/linux/man-pages/man1/date.1.html
61+
- name: "Get last Monday's date"
62+
id: get-date
63+
run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> "$GITHUB_OUTPUT"
64+
65+
# Since Composer dependencies are installed using `composer update` and no lock file is in version control,
66+
# passing a custom cache suffix ensures that the cache is flushed at least once per week.
67+
- name: Install Composer dependencies
68+
uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # v3.1.1
69+
with:
70+
custom-cache-suffix: ${{ steps.get-date.outputs.date }}
71+
72+
- name: Make Composer packages available globally
73+
run: echo "${PWD}/vendor/bin" >> "$GITHUB_PATH"
74+
75+
- name: Cache PHP Static Analysis scan cache
76+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
77+
with:
78+
path: .cache # This is defined in the base.neon file.
79+
key: "phpstan-result-cache-${{ github.run_id }}"
80+
restore-keys: |
81+
phpstan-result-cache-
82+
83+
- name: Run PHP static analysis tests
84+
id: phpstan
85+
run: phpstan analyse -vvv --error-format=checkstyle | cs2pr
86+
87+
- name: "Save result cache"
88+
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
89+
if: ${{ !cancelled() }}
90+
with:
91+
path: .cache
92+
key: "phpstan-result-cache-${{ github.run_id }}"
93+
94+
- name: Ensure version-controlled files are not modified or deleted
95+
run: git diff --exit-code

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ wp-tests-config.php
2222
/build
2323
/tests/phpunit/build
2424
/wp-cli.local.yml
25+
/phpstan.neon
2526
/jsdoc
2627
/composer.lock
2728
/vendor

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"squizlabs/php_codesniffer": "3.13.2",
2424
"wp-coding-standards/wpcs": "~3.2.0",
2525
"phpcompatibility/phpcompatibility-wp": "~2.1.3",
26+
"phpstan/phpstan": "~1.12.32",
2627
"yoast/phpunit-polyfills": "^1.1.0"
2728
},
2829
"config": {
@@ -32,6 +33,7 @@
3233
"lock": false
3334
},
3435
"scripts": {
36+
"analyse": "@php ./vendor/bin/phpstan analyse --memory-limit=2G",
3537
"compat": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcompat.xml.dist --report=summary,source",
3638
"format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source",
3739
"lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@
192192
"env:logs": "node ./tools/local-env/scripts/docker.js logs",
193193
"env:pull": "node ./tools/local-env/scripts/docker.js pull",
194194
"test:performance": "wp-scripts test-playwright --config tests/performance/playwright.config.js",
195+
"test:php:stan": "node ./tools/local-env/scripts/docker.js run --rm php ./vendor/bin/phpstan analyse --memory-limit=2G",
195196
"test:php": "node ./tools/local-env/scripts/docker.js run --rm php ./vendor/bin/phpunit",
196197
"test:coverage": "npm run test:php -- --coverage-html ./coverage/html/ --coverage-php ./coverage/php/report.php --coverage-text=./coverage/text/report.txt",
197198
"test:e2e": "wp-scripts test-playwright --config tests/e2e/playwright.config.js",

phpcs.xml.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
<exclude-pattern>/tests/phpunit/build*</exclude-pattern>
8282
<exclude-pattern>/tests/phpunit/data/*</exclude-pattern>
8383

84+
<!-- PHPStan bootstrap, stubs, and baseline. -->
85+
<exclude-pattern>/tests/phpstan/*</exclude-pattern>
86+
8487
<exclude-pattern>/tools/*</exclude-pattern>
8588

8689
<!-- Drop-in plugins. -->

phpstan.neon.dist

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# PHPStan configuration for WordPress Core.
2+
#
3+
# To overload this configuration, copy this file to phpstan.neon and adjust as needed.
4+
#
5+
# https://phpstan.org/config-reference
6+
7+
includes:
8+
# The WordPress Core configuration file includes the base configuration for the WordPress codebase.
9+
- tests/phpstan/base.neon
10+
# The baseline file includes preexisting errors in the codebase that should be ignored.
11+
# https://phpstan.org/user-guide/baseline
12+
- tests/phpstan/baseline.php
13+
14+
parameters:
15+
# https://phpstan.org/user-guide/rule-levels
16+
level: 0
17+
reportUnmatchedIgnoredErrors: false
18+
19+
ignoreErrors:
20+
# Level 0:
21+
- # Inner functions arent supported by PHPstan.
22+
message: '#Function wxr_[a-z_]+ not found#'
23+
path: src/wp-admin/includes/export.php
24+
-
25+
identifier: function.inner
26+
path: src/wp-admin/includes/export.php
27+
count: 13
28+
-
29+
identifier: function.inner
30+
path: src/wp-admin/includes/file.php
31+
count: 1
32+
-
33+
identifier: function.inner
34+
path: src/wp-includes/canonical.php
35+
count: 1

src/wp-admin/includes/class-wp-filesystem-ssh2.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ public function size( $file ) {
672672
* Default 0.
673673
*/
674674
public function touch( $file, $time = 0, $atime = 0 ) {
675+
// @phpstan-ignore-next-line
675676
// Not implemented.
676677
}
677678

src/wp-admin/press-this.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function wp_load_press_this() {
2323
);
2424
} elseif ( is_plugin_active( $plugin_file ) ) {
2525
include WP_PLUGIN_DIR . '/press-this/class-wp-press-this-plugin.php';
26-
$wp_press_this = new WP_Press_This_Plugin();
26+
$wp_press_this = new WP_Press_This_Plugin(); // @phpstan-ignore class.notFound
2727
$wp_press_this->html();
2828
} elseif ( current_user_can( 'activate_plugins' ) ) {
2929
if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin_file ) ) {

src/wp-includes/class-wp-theme-json.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3365,7 +3365,7 @@ public function get_svg_filters( $origins ) {
33653365
* @param array $theme_json The theme.json like structure to inspect.
33663366
* @param array $path Path to inspect.
33673367
* @param bool|array $override Data to compute whether to override the preset.
3368-
* @return bool
3368+
* @return bool|null True if the preset should override the defaults, false if not. Null if the override parameter is invalid.
33693369
*/
33703370
protected static function should_override_preset( $theme_json, $path, $override ) {
33713371
_deprecated_function( __METHOD__, '6.0.0', 'get_metadata_boolean' );
@@ -3400,6 +3400,8 @@ protected static function should_override_preset( $theme_json, $path, $override
34003400

34013401
return true;
34023402
}
3403+
3404+
return null;
34033405
}
34043406

34053407
/**

0 commit comments

Comments
 (0)