Make WordPress Core

Changeset 61699


Ignore:
Timestamp:
02/20/2026 02:24:12 AM (2 weeks ago)
Author:
westonruter
Message:

Build/Test Tools: Integrate PHPStan into the core development workflow.

This change introduces PHPStan static analysis configured at rule level 0, which includes: "basic checks, unknown classes, unknown functions, unknown methods called on $this, wrong number of arguments passed to those methods and functions, always undefined variables". Contributors may elect for a higher PHPStan rule level by creating a phpstan.neon which overrides phpstan.neon.dist.

  • Fix various PHPStan level 0 errors by adding @phpstan-ignore comments, updating PHPDoc types, and adding missing return values.
  • Remove existing @phpstan-ignore comments that are now obsolete or inapplicable for level 0.
  • Add a new GitHub Actions workflow for PHPStan Static Analysis. Reports are currently provided as warnings with inline annotations in pull requests and do not fail the build.
  • Add a phpstan Grunt task and include it in the precommit:php task to run before phpunit.
  • Introduce a typecheck:php npm script and a composer phpstan script to run analysis in local development environments.
  • Add documentation for PHPStan usage in tests/phpstan/README.md.

Developed in https://github.com/WordPress/wordpress-develop/pull/10419

Props justlevine, westonruter, johnbillion, desrosj, SirLouen, dmsnell, oglekler, joehoyle, jorbin.
See #64238, #63268, #52217, #51423.
Fixes #61175.

Location:
trunk
Files:
8 added
15 edited

Legend:

Unmodified
Added
Removed
  • trunk

    • Property svn:ignore
      •  

        old new  
        1111wp-cli.local.yml
        1212.git
         13phpstan.neon
        1314jsdoc
        1415composer.lock
  • trunk/.gitignore

    r61674 r61699  
    2424/tests/phpunit/build
    2525/wp-cli.local.yml
     26/phpstan.neon
    2627/jsdoc
    2728/composer.lock
  • trunk/Gruntfile.js

    r61669 r61699  
    15611561
    15621562    grunt.registerTask( 'precommit:php', [
     1563        'phpstan',
    15631564        'phpunit'
    15641565    ] );
     
    20022003    grunt.registerTask( 'test', 'Runs all QUnit and PHPUnit tasks.', ['qunit:compiled', 'phpunit'] );
    20032004
     2005    grunt.registerTask( 'phpstan', 'Runs PHPStan on the entire codebase.', function() {
     2006        var done = this.async();
     2007
     2008        grunt.util.spawn( {
     2009            cmd: 'composer',
     2010            args: [ 'phpstan' ],
     2011            opts: { stdio: 'inherit' }
     2012        }, function( error ) {
     2013            done( ! error );
     2014        } );
     2015    } );
     2016
    20042017    grunt.registerTask( 'format:php', 'Runs the code formatter on changed files.', function() {
    20052018        var done = this.async();
  • trunk/composer.json

    r61685 r61699  
    2424        "wp-coding-standards/wpcs": "~3.3.0",
    2525        "phpcompatibility/phpcompatibility-wp": "~2.1.3",
     26        "phpstan/phpstan": "2.1.39",
    2627        "yoast/phpunit-polyfills": "^1.1.0"
    2728    },
     
    3334    },
    3435    "scripts": {
     36        "phpstan": "@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",
  • trunk/package.json

    r61686 r61699  
    131131        "test:e2e": "wp-scripts test-playwright --config tests/e2e/playwright.config.js",
    132132        "test:visual": "wp-scripts test-playwright --config tests/visual-regression/playwright.config.js",
     133        "typecheck:php": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan",
    133134        "gutenberg:checkout": "node tools/gutenberg/checkout-gutenberg.js",
    134135        "gutenberg:build": "node tools/gutenberg/build-gutenberg.js",
  • trunk/phpcs.xml.dist

    r60939 r61699  
    8181    <exclude-pattern>/tests/phpunit/build*</exclude-pattern>
    8282    <exclude-pattern>/tests/phpunit/data/*</exclude-pattern>
     83
     84    <!-- PHPStan bootstrap, stubs, and baseline. -->
     85    <exclude-pattern>/tests/phpstan/*</exclude-pattern>
    8386
    8487    <exclude-pattern>/tools/*</exclude-pattern>
  • trunk/src/wp-admin/includes/class-wp-filesystem-ssh2.php

    r57644 r61699  
    671671     * @param int    $atime Optional. Access time to set for file.
    672672     *                      Default 0.
     673     * @return false Always returns false because not implemented.
    673674     */
    674675    public function touch( $file, $time = 0, $atime = 0 ) {
    675676        // Not implemented.
     677        return false;
    676678    }
    677679
  • trunk/src/wp-admin/press-this.php

    r61030 r61699  
    2323        );
    2424    } elseif ( is_plugin_active( $plugin_file ) ) {
    25         include WP_PLUGIN_DIR . '/press-this/class-wp-press-this-plugin.php';
    26         $wp_press_this = new WP_Press_This_Plugin();
     25        include WP_PLUGIN_DIR . '/press-this/class-wp-press-this-plugin.php'; // @phpstan-ignore include.fileNotFound
     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' ) ) {
  • trunk/src/wp-includes/class-wp-scripts.php

    r61587 r61699  
    11871187            }
    11881188        }
    1189         $stored_results[ $handle ] = $priorities[ $highest_priority_index ]; // @phpstan-ignore parameterByRef.type (We know the index is valid and that this will be a string.)
     1189        $stored_results[ $handle ] = $priorities[ $highest_priority_index ];
    11901190        return $priorities[ $highest_priority_index ];
    11911191    }
  • trunk/src/wp-includes/class-wp-theme-json.php

    r61678 r61699  
    35433543     * @param array      $path       Path to inspect.
    35443544     * @param bool|array $override   Data to compute whether to override the preset.
    3545      * @return bool
     3545     * @return bool|null True if the preset should override the defaults, false if not. Null if the override parameter is invalid.
    35463546     */
    35473547    protected static function should_override_preset( $theme_json, $path, $override ) {
     
    35783578            return true;
    35793579        }
     3580
     3581        return null;
    35803582    }
    35813583
  • trunk/src/wp-includes/functions.php

    r61692 r61699  
    37663766 *     @type bool   $exit           Whether to exit the process after completion. Default true.
    37673767 * }
     3768 * @return never|void Returns void if `$args['exit']` is false, otherwise exits.
     3769 *
     3770 * @phpstan-return ( $args['exit'] is false ? void : never )
    37683771 */
    37693772function wp_die( $message = '', $title = '', $args = array() ) {
  • trunk/src/wp-includes/html-api/class-wp-html-processor.php

    r60919 r61699  
    140140 * @see WP_HTML_Tag_Processor
    141141 * @see https://html.spec.whatwg.org/
     142 * @phpstan-consistent-constructor
    142143 */
    143144class WP_HTML_Processor extends WP_HTML_Tag_Processor {
     
    584585     *
    585586     * @param string $message Explains support is missing in order to parse the current node.
     587     * @return never
    586588     */
    587589    private function bail( string $message ) {
  • trunk/src/wp-includes/media.php

    r61501 r61699  
    41174117 */
    41184118function is_gd_image( $image ) {
    4119     if ( $image instanceof GdImage
     4119    if ( $image instanceof GdImage // @phpstan-ignore class.notFound (Only available with PHP8+.)
    41204120        || is_resource( $image ) && 'gd' === get_resource_type( $image )
    41214121    ) {
  • trunk/src/wp-includes/style-engine/class-wp-style-engine-css-rules-store.php

    r58089 r61699  
    1414 *
    1515 * @since 6.1.0
     16 *
     17 * @phpstan-consistent-constructor
    1618 */
    1719#[AllowDynamicProperties]
  • trunk/src/wp-includes/template.php

    r61139 r61699  
    797797
    798798    if ( isset( $s ) ) {
    799         $s = esc_attr( $s );
     799        $s = esc_attr( $s ); // @phpstan-ignore variable.undefined (It's extracted from query vars.)
    800800    }
    801801
     
    977977
    978978            // Display a caught exception as an error since it prevents any of the output buffer filters from applying.
    979             if ( $did_just_catch ) { // @phpstan-ignore if.alwaysFalse (The variable is set in the catch block below.)
     979            if ( $did_just_catch ) {
    980980                $level = E_USER_ERROR;
    981981            }
Note: See TracChangeset for help on using the changeset viewer.