Make WordPress Core

Changeset 63420


Ignore:
Timestamp:
09/01/2026 09:29:49 AM (6 weeks ago)
Author:
swissspidy
Message:

Build/Test Tools: Teach PHPStan to read hash notation.

Core describes what an array or object holds with hash notation, the nested @type list that PHPStan reads as free text. A parser node visitor now translates each one into the array or object shape it stands for, so the analysis gets the types the documentation already carries. Existing @phpstan-param or @phpstan-return tags take precedence.

Some incorrect/unclear docblocks were fixed in the process.

The GitHub Actions workflow also keys its PHPStan cache on phpstan.neon.dist and the sources in tests/phpstan now to avoid stale results.

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

Props swissspidy, westonruter, szepeviktor.
See #65817.

Location:
trunk
Files:
1 added
18 edited

Legend:

Unmodified
Added
Removed
  • trunk/.github/workflows/reusable-phpstan-static-analysis-v1.yml

    r63019 r63420  
    8585        run: npm run build:dev
    8686
     87      # The directory holds more than the analysis results. PHPStan also stores what it read out of
     88      # each source file there, the docblocks and signatures it found, keyed by that file's contents
     89      # and nothing else. The extensions in `tests/phpstan` change what reading a file yields without
     90      # changing the file: a parser node visitor rewrites a docblock in the syntax tree, and the bytes
     91      # on disk stay as they were. A cache written before one of them changed therefore answers with
     92      # what the old code saw, and the analysis silently runs against types no longer derived.
     93      #
     94      # Keying on the configuration and the extensions keeps a run from restoring a cache that
     95      # predates either. The baselines are left out of the key: they only decide which reported errors
     96      # are ignored, PHPStan invalidates the results cache on a configuration change by itself, and
     97      # including them would discard the whole cache every time one is regenerated.
     98      #
     99      # The cache is renamed rather than only prefixed, because the key this replaces restored on
     100      # `phpstan-result-cache-`, which every key below still begins with. A branch that has not
     101      # merged this commit yet runs the workflow it has, and that workflow would prefix-match the
     102      # caches written here: it would analyse without the extensions against reflection derived with
     103      # them. A name of its own leaves those runs restoring only their own caches.
    87104      - name: Cache PHP Static Analysis scan cache
    88105        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
    89106        with:
    90107          path: .cache # This is defined in the base.neon file.
    91           key: "phpstan-result-cache-${{ github.run_id }}"
     108          key: "phpstan-analysis-cache-${{ hashFiles('phpstan.neon.dist', 'tests/phpstan/*.neon', 'tests/phpstan/*.php') }}-${{ github.run_id }}"
    92109          restore-keys: |
    93             phpstan-result-cache-
     110            phpstan-analysis-cache-${{ hashFiles('phpstan.neon.dist', 'tests/phpstan/*.neon', 'tests/phpstan/*.php') }}-
    94111
    95112      - name: Run PHP static analysis tests
    … …  
    194211        with:
    195212          path: .cache
    196           key: "phpstan-result-cache-${{ github.run_id }}"
     213          key: "phpstan-analysis-cache-${{ hashFiles('phpstan.neon.dist', 'tests/phpstan/*.neon', 'tests/phpstan/*.php') }}-${{ github.run_id }}"
    197214
    198215      - name: Ensure version-controlled files are not modified or deleted
  • trunk/src/wp-admin/includes/class-wp-list-table.php

    r62868 r63420  
    428428         *
    429429         * @param array $link_data {
    430          *     An array of link data.
    431          *
    432          *     @type string $url     The link URL.
    433          *     @type string $label   The link label.
    434          *     @type bool   $current Optional. Whether this is the currently selected view.
     430         *     An array of link data, keyed by view.
     431         *
     432         *     @type array ...$0 {
     433         *         Data for a single view link.
     434         *
     435         *         @type string $url     The link URL.
     436         *         @type string $label   The link label.
     437         *         @type bool   $current Optional. Whether this is the currently selected view.
     438         *     }
    435439         * }
    436440         * @return string[] An array of link markup. Keys match the `$link_data` input array.
  • trunk/src/wp-admin/includes/misc.php

    r62334 r63420  
    15711571 *     Array of PHP version data. False on failure.
    15721572 *
    1573  *     @type string $recommended_version The PHP version recommended by WordPress.
    1574  *     @type string $minimum_version     The minimum required PHP version.
    1575  *     @type bool   $is_supported        Whether the PHP version is actively supported.
    1576  *     @type bool   $is_secure           Whether the PHP version receives security updates.
    1577  *     @type bool   $is_acceptable       Whether the PHP version is still acceptable or warnings
    1578  *                                       should be shown and an update recommended.
     1573 *     @type string $recommended_version          The PHP version recommended by WordPress.
     1574 *     @type string $minimum_version              The minimum required PHP version.
     1575 *     @type bool   $is_supported                 Whether the PHP version is actively supported.
     1576 *     @type bool   $is_secure                    Whether the PHP version receives security updates.
     1577 *     @type bool   $is_acceptable                Whether the PHP version is still acceptable or warnings
     1578 *                                                should be shown and an update recommended.
     1579 *     @type bool   $is_lower_than_future_minimum Whether the PHP version is lower than the minimum PHP
     1580 *                                                version WordPress will require in a future release.
    15791581 * }
    15801582 */
  • trunk/src/wp-admin/includes/post.php

    r62704 r63420  
    13941394 *                       Defaults to the `$_GET` superglobal.
    13951395 * @return array {
    1396  *     Array containing the post mime types and available post mime types.
    1397  *
    1398  *     @type array[]  $post_mime_types       Post mime types.
    1399  *     @type string[] $avail_post_mime_types Available post mime types.
     1396 *     Array containing the post mime types and the available post mime types, in that order.
     1397 *
     1398 *     @type array<string, array{0: string, 1: string, 2: array}> $0 Post mime types. See get_post_mime_types().
     1399 *     @type string[]                                             $1 Available post mime types.
    14001400 * }
    14011401 */
  • trunk/src/wp-includes/class-wp-date-query.php

    r61724 r63420  
    7474         *     Array of date query clauses.
    7575         *
    76          *     @type array ...$0 {
    77          *         @type string $column   Optional. The column to query against. If undefined, inherits the value of
    78          *                                the `$default_column` parameter. See WP_Date_Query::validate_column() and
    79          *                                the {@see 'date_query_valid_columns'} filter for the list of accepted values.
    80          *                                Default 'post_date'.
    81          *         @type string $compare  Optional. The comparison operator. Accepts '=', '!=', '>', '>=', '<', '<=',
    82          *                                'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default '='.
    83          *         @type string $relation Optional. The boolean relationship between the date queries. Accepts 'OR' or 'AND'.
    84          *                                Default 'OR'.
    85          *         @type array  ...$0 {
    86          *             Optional. An array of first-order clause parameters, or another fully-formed date query.
    87          *
    88          *             @type string|array $before {
    89          *                 Optional. Date to retrieve posts before. Accepts `strtotime()`-compatible string,
    90          *                 or array of 'year', 'month', 'day' values.
    91          *
    92          *                 @type string $year  The four-digit year. Default empty. Accepts any four-digit year.
    93          *                 @type string $month Optional when passing array. The month of the year.
    94          *                                     Default (string:empty)|(array:1). Accepts numbers 1-12.
    95          *                 @type string $day   Optional when passing array. The day of the month.
    96          *                                     Default (string:empty)|(array:1). Accepts numbers 1-31.
    97          *             }
    98          *             @type string|array $after {
    99          *                 Optional. Date to retrieve posts after. Accepts `strtotime()`-compatible string,
    100          *                 or array of 'year', 'month', 'day' values.
    101          *
    102          *                 @type string $year  The four-digit year. Accepts any four-digit year. Default empty.
    103          *                 @type string $month Optional when passing array. The month of the year. Accepts numbers 1-12.
    104          *                                     Default (string:empty)|(array:12).
    105          *                 @type string $day   Optional when passing array. The day of the month. Accepts numbers 1-31.
    106          *                                     Default (string:empty)|(array:last day of month).
    107          *             }
    108          *             @type string       $column        Optional. Used to add a clause comparing a column other than
    109          *                                               the column specified in the top-level `$column` parameter.
    110          *                                               See WP_Date_Query::validate_column() and
    111          *                                               the {@see 'date_query_valid_columns'} filter for the list
    112          *                                               of accepted values. Default is the value of top-level `$column`.
    113          *             @type string       $compare       Optional. The comparison operator. Accepts '=', '!=', '>', '>=',
    114          *                                               '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Comparisons
    115          *                                               support arrays in some time-related parameters. Default '='.
    116          *             @type bool         $inclusive     Optional. Include results from dates specified in 'before' or
    117          *                                               'after'. Default false.
    118          *             @type int|int[]    $year          Optional. The four-digit year number. Accepts any four-digit year
    119          *                                               or an array of years if `$compare` supports it. Default empty.
    120          *             @type int|int[]    $month         Optional. The two-digit month number. Accepts numbers 1-12 or an
    121          *                                               array of valid numbers if `$compare` supports it. Default empty.
    122          *             @type int|int[]    $week          Optional. The week number of the year. Accepts numbers 1-53 or an
    123          *                                               array of valid numbers if `$compare` supports it. Default empty.
    124          *             @type int|int[]    $dayofyear     Optional. The day number of the year. Accepts numbers 1-366 or an
    125          *                                               array of valid numbers if `$compare` supports it.
    126          *             @type int|int[]    $day           Optional. The day of the month. Accepts numbers 1-31 or an array
    127          *                                               of valid numbers if `$compare` supports it. Default empty.
    128          *             @type int|int[]    $dayofweek     Optional. The day number of the week. Accepts numbers 1-7 (1 is
    129          *                                               Sunday) or an array of valid numbers if `$compare` supports it.
    130          *                                               Default empty.
    131          *             @type int|int[]    $dayofweek_iso Optional. The day number of the week (ISO). Accepts numbers 1-7
    132          *                                               (1 is Monday) or an array of valid numbers if `$compare` supports it.
    133          *                                               Default empty.
    134          *             @type int|int[]    $hour          Optional. The hour of the day. Accepts numbers 0-23 or an array
    135          *                                               of valid numbers if `$compare` supports it. Default empty.
    136          *             @type int|int[]    $minute        Optional. The minute of the hour. Accepts numbers 0-59 or an array
    137          *                                               of valid numbers if `$compare` supports it. Default empty.
    138          *             @type int|int[]    $second        Optional. The second of the minute. Accepts numbers 0-59 or an
    139          *                                               array of valid numbers if `$compare` supports it. Default empty.
     76         *     @type string $column   Optional. The column to query against. If undefined, inherits the value of
     77         *                            the `$default_column` parameter. See WP_Date_Query::validate_column() and
     78         *                            the {@see 'date_query_valid_columns'} filter for the list of accepted values.
     79         *                            Default 'post_date'.
     80         *     @type string $compare  Optional. The comparison operator. Accepts '=', '!=', '>', '>=', '<', '<=',
     81         *                            'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default '='.
     82         *     @type string $relation Optional. The boolean relationship between the date queries. Accepts 'OR' or 'AND'.
     83         *                            Default 'OR'.
     84         *     @type array  ...$0 {
     85         *         Optional. An array of first-order clause parameters, or another fully-formed date query.
     86         *
     87         *         @type string|array $before {
     88         *             Optional. Date to retrieve posts before. Accepts `strtotime()`-compatible string,
     89         *             or array of 'year', 'month', 'day' values.
     90         *
     91         *             @type string $year  The four-digit year. Default empty. Accepts any four-digit year.
     92         *             @type string $month Optional when passing array. The month of the year.
     93         *                                 Default (string:empty)|(array:1). Accepts numbers 1-12.
     94         *             @type string $day   Optional when passing array. The day of the month.
     95         *                                 Default (string:empty)|(array:1). Accepts numbers 1-31.
    14096         *         }
     97         *         @type string|array $after {
     98         *             Optional. Date to retrieve posts after. Accepts `strtotime()`-compatible string,
     99         *             or array of 'year', 'month', 'day' values.
     100         *
     101         *             @type string $year  The four-digit year. Accepts any four-digit year. Default empty.
     102         *             @type string $month Optional when passing array. The month of the year. Accepts numbers 1-12.
     103         *                                 Default (string:empty)|(array:12).
     104         *             @type string $day   Optional when passing array. The day of the month. Accepts numbers 1-31.
     105         *                                 Default (string:empty)|(array:last day of month).
     106         *         }
     107         *         @type string       $column        Optional. Used to add a clause comparing a column other than
     108         *                                           the column specified in the top-level `$column` parameter.
     109         *                                           See WP_Date_Query::validate_column() and
     110         *                                           the {@see 'date_query_valid_columns'} filter for the list
     111         *                                           of accepted values. Default is the value of top-level `$column`.
     112         *         @type string       $compare       Optional. The comparison operator. Accepts '=', '!=', '>', '>=',
     113         *                                           '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Comparisons
     114         *                                           support arrays in some time-related parameters. Default '='.
     115         *         @type bool         $inclusive     Optional. Include results from dates specified in 'before' or
     116         *                                           'after'. Default false.
     117         *         @type int|int[]    $year          Optional. The four-digit year number. Accepts any four-digit year
     118         *                                           or an array of years if `$compare` supports it. Default empty.
     119         *         @type int|int[]    $month         Optional. The two-digit month number. Accepts numbers 1-12 or an
     120         *                                           array of valid numbers if `$compare` supports it. Default empty.
     121         *         @type int|int[]    $week          Optional. The week number of the year. Accepts numbers 1-53 or an
     122         *                                           array of valid numbers if `$compare` supports it. Default empty.
     123         *         @type int|int[]    $dayofyear     Optional. The day number of the year. Accepts numbers 1-366 or an
     124         *                                           array of valid numbers if `$compare` supports it.
     125         *         @type int|int[]    $day           Optional. The day of the month. Accepts numbers 1-31 or an array
     126         *                                           of valid numbers if `$compare` supports it. Default empty.
     127         *         @type int|int[]    $dayofweek     Optional. The day number of the week. Accepts numbers 1-7 (1 is
     128         *                                           Sunday) or an array of valid numbers if `$compare` supports it.
     129         *                                           Default empty.
     130         *         @type int|int[]    $dayofweek_iso Optional. The day number of the week (ISO). Accepts numbers 1-7
     131         *                                           (1 is Monday) or an array of valid numbers if `$compare` supports it.
     132         *                                           Default empty.
     133         *         @type int|int[]    $hour          Optional. The hour of the day. Accepts numbers 0-23 or an array
     134         *                                           of valid numbers if `$compare` supports it. Default empty.
     135         *         @type int|int[]    $minute        Optional. The minute of the hour. Accepts numbers 0-59 or an array
     136         *                                           of valid numbers if `$compare` supports it. Default empty.
     137         *         @type int|int[]    $second        Optional. The second of the minute. Accepts numbers 0-59 or an
     138         *                                           array of valid numbers if `$compare` supports it. Default empty.
    141139         *     }
    142140         * }
  • trunk/src/wp-includes/class-wp-http.php

    r62649 r63420  
    154154         *     Array of response data, or a WP_Error instance upon error.
    155155         *
    156          *     @type \WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers       Response headers keyed by name.
    157          *     @type string                                            $body          Response body.
    158          *     @type array                                             $response      {
     156         *     @type \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array $headers       Response headers keyed by name.
     157         *                                                                                  An empty array for a non-blocking request.
     158         *     @type string                                                 $body          Response body.
     159         *     @type array                                                  $response      {
    159160         *         Array of HTTP response data.
    160161         *
    … …  
    162163         *         @type string|false $message HTTP response message.
    163164         *     }
    164          *     @type WP_Http_Cookie[]                                  $cookies       Array of cookies set by the server.
    165          *     @type string|null                                       $filename      Optional. Filename of the response.
    166          *     @type WP_HTTP_Requests_Response|null                    $http_response Response object.
     165         *     @type WP_Http_Cookie[]                                       $cookies       Array of cookies set by the server.
     166         *     @type string|null                                            $filename      Optional. Filename of the response.
     167         *     @type WP_HTTP_Requests_Response|null                         $http_response Response object.
    167168         * }
    168169         */
    … …  
    712713         *     then a numbered array is returned as the value of that header-key.
    713714         *
    714          *     @type array            $response {
     715         *     @type array                         $response {
    715716         *         @type int    $code    The response status code. Default 0.
    716717         *         @type string $message The response message. Default empty.
    717718         *     }
    718          *     @type array            $newheaders The processed header data as a multidimensional array.
    719          *     @type WP_Http_Cookie[] $cookies    If the original headers contain the 'Set-Cookie' key,
    720          *                                        an array containing `WP_Http_Cookie` objects is returned.
     719         *     @type array<string, string|string[]> $headers  The processed header data, keyed by lowercased header name.
     720         *     @type WP_Http_Cookie[]               $cookies  If the original headers contain the 'Set-Cookie' key,
     721         *                                                    an array containing `WP_Http_Cookie` objects is returned.
    721722         * }
    722723         */
  • trunk/src/wp-includes/class-wp-xmlrpc-server.php

    r63393 r63420  
    32513251         *     @type string $2 Username.
    32523252         *     @type string $3 Password.
    3253          *     @type string $4 Content.
     3253         *     @type array  $4 Content struct, with keys documented on {@see self::mw_newPost()}.
    32543254         *     @type int    $5 Publish flag. 0 for draft, 1 for publish.
    32553255         * }
  • trunk/src/wp-includes/class-wpdb.php

    r62529 r63420  
    20662066         *
    20672067         *     @type string      $0 Host name.
    2068          *     @type string|null $1 Port.
     2068         *     @type int|null    $1 Port.
    20692069         *     @type string|null $2 Socket.
    20702070         *     @type bool        $3 Whether it is an IPv6 address.
  • trunk/src/wp-includes/cron.php

    r62894 r63420  
    766766 * @param int|null $timestamp Optional. Unix timestamp (UTC) of the event. If not specified, the next scheduled event
    767767 *                            is returned. Default null.
    768  * @return object|false {
     768 * @return stdClass|false {
    769769 *     The event object. False if the event does not exist.
    770770 *
  • trunk/src/wp-includes/functions.php

    r63380 r63420  
    23472347 *                     path: non-empty-string,
    23482348 *                     url: non-empty-string,
    2349  *                     subdir: non-empty-string,
     2349 *                     subdir: string,
    23502350 *                     basedir: non-empty-string,
    23512351 *                     baseurl: non-empty-string,
     2352 *                     error: non-empty-string|false,
    23522353 *                 }
    2353  *                |array{ error: non-empty-string }
    23542354 */
    23552355function wp_get_upload_dir() {
    … …  
    23962396 *                     path: non-empty-string,
    23972397 *                     url: non-empty-string,
    2398  *                     subdir: non-empty-string,
     2398 *                     subdir: string,
    23992399 *                     basedir: non-empty-string,
    24002400 *                     baseurl: non-empty-string,
     2401 *                     error: non-empty-string|false,
    24012402 *                 }
    2402  *                |array{ error: non-empty-string }
    24032403 */
    24042404function wp_upload_dir( $time = null, $create_dir = true, $refresh_cache = false ) {
    … …  
    24642464 * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null.
    24652465 * @return array See wp_upload_dir()
     2466 * @phpstan-return array{
     2467 *                     path: non-empty-string,
     2468 *                     url: non-empty-string,
     2469 *                     subdir: string,
     2470 *                     basedir: non-empty-string,
     2471 *                     baseurl: non-empty-string,
     2472 *                     error: false,
     2473 *                 }
    24662474 */
    24672475function _wp_upload_dir( $time = null ) {
    … …  
    29162924 *     Information about the newly-uploaded file.
    29172925 *
    2918  *     @type string       $file  Filename of the newly-uploaded file.
    2919  *     @type string       $url   URL of the uploaded file.
    2920  *     @type string       $type  File type.
     2926 *     @type string       $file  Optional. Filename of the newly-uploaded file. Not set if there has been an error.
     2927 *     @type string       $url   Optional. URL of the uploaded file. Not set if there has been an error.
     2928 *     @type string|false $type  Optional. File type, or false if the file doesn't match a mime type.
     2929 *                               Not set if there has been an error.
    29212930 *     @type string|false $error Error message, if there has been an error.
    29222931 * }
     2932 * @phpstan-return array{ file: non-empty-string, url: non-empty-string, type: string|false, error: false }
     2933 *                |array{ error: string, ... }
    29232934 */
    29242935function wp_upload_bits( $name, $deprecated, $bits, $time = null ) {
    … …  
    37993810 *     as the response code. Default empty array.
    38003811 *
    3801  *     @type int    $response       The HTTP response code. Default 200 for Ajax requests, 500 otherwise.
    3802  *     @type string $link_url       A URL to include a link to. Only works in combination with $link_text.
    3803  *                                  Default empty string.
    3804  *     @type string $link_text      A label for the link to include. Only works in combination with $link_url.
    3805  *                                  Default empty string.
    3806  *     @type bool   $back_link      Whether to include a link to go back. Default false.
    3807  *     @type string $text_direction The text direction. This is only useful internally, when WordPress is still
    3808  *                                  loading and the site's locale is not set up yet. Accepts 'rtl' and 'ltr'.
    3809  *                                  Default is the value of is_rtl().
    3810  *     @type string $charset        Character set of the HTML output. Default 'utf-8'.
    3811  *     @type string $code           Error code to use. Default is 'wp_die', or the main error code if $message
    3812  *                                  is a WP_Error.
    3813  *     @type bool   $exit           Whether to exit the process after completion. Default true.
     3812 *     @type int|null $response       The HTTP response code, or null to send no status header. The Ajax, JSON,
     3813 *                                    JSONP and XML handlers all accept null, for backward compatibility.
     3814 *                                    Default 200 for Ajax requests, 500 otherwise.
     3815 *     @type string   $link_url       A URL to include a link to. Only works in combination with $link_text.
     3816 *                                    Default empty string.
     3817 *     @type string   $link_text      A label for the link to include. Only works in combination with $link_url.
     3818 *                                    Default empty string.
     3819 *     @type bool     $back_link      Whether to include a link to go back. Default false.
     3820 *     @type string   $text_direction The text direction. This is only useful internally, when WordPress is still
     3821 *                                    loading and the site's locale is not set up yet. Accepts 'rtl' and 'ltr'.
     3822 *                                    Default is the value of is_rtl().
     3823 *     @type string   $charset        Character set of the HTML output. Default 'utf-8'.
     3824 *     @type string   $code           Error code to use. Default is 'wp_die', or the main error code if $message
     3825 *                                    is a WP_Error.
     3826 *     @type bool     $exit           Whether to exit the process after completion. Default true.
    38143827 * }
    38153828 * @return void Never returns if `$args['exit']` is true (the default), otherwise returns void.
  • trunk/src/wp-includes/link-template.php

    r63379 r63420  
    44264426 *     @type string|false $url          The URL of the avatar that was found, or false.
    44274427 * }
     4428 * @phpstan-return array{ found_avatar: bool, url: string|false, ... }
    44284429 */
    44294430function get_avatar_data( $id_or_email, $args = null ) {
  • trunk/src/wp-includes/post.php

    r63419 r63420  
    20132013 *
    20142014 * @param object $args Post type registration arguments.
    2015  * @return object {
     2015 * @return stdClass {
    20162016 *     Object with all the capabilities as member variables.
    20172017 *
    … …  
    36303630 * @since 5.3.0 Added the 'Documents', 'Spreadsheets', and 'Archives' mime type groups.
    36313631 *
    3632  * @return array List of post mime types.
     3632 * @return array<string, array{0: string, 1: string, 2: array}> List of post mime types, keyed by mime type group
     3633 *                                                              or by a comma-separated list of mime types. Each
     3634 *                                                              value is a three-item array: the plural name of the
     3635 *                                                              group, the label for its "Manage" screen, and the
     3636 *                                                              translatable count strings returned by _n_noop().
    36333637 */
    36343638function get_post_mime_types() {
    … …  
    37233727         * @since 2.5.0
    37243728         *
    3725          * @param array $post_mime_types Default list of post mime types.
     3729         * @param array<string, array{0: string, 1: string, 2: array}> $post_mime_types Default list of post mime types.
     3730         *                                                                              See {@see get_post_mime_types()}.
    37263731         */
    37273732        return apply_filters( 'post_mime_types', $post_mime_types );
  • trunk/src/wp-includes/taxonomy.php

    r63419 r63420  
    651651 *
    652652 * @param WP_Taxonomy $tax Taxonomy object.
    653  * @return object {
     653 * @return stdClass {
    654654 *     Taxonomy labels object. The first default value is for non-hierarchical taxonomies
    655655 *     (like tags) and the second one is for hierarchical taxonomies (like categories).
  • trunk/tests/phpstan/README.md

    r63019 r63420  
    5959
    6060Core documents the globals a function uses with `@global Type $varname`. `GlobalDocBlockVisitor` bridges that convention to PHPStan's variable type resolution, so those globals are typed rather than `mixed` inside the function.
     61
     62### Hash notation
     63
     64Core documents the contents of an array or object with a nested list of `@type` tags, [hash notation](https://developer.wordpress.org/coding-standards/inline-documentation-standards/php/#1-1-parameters-that-are-arrays):
     65
     66```php
     67/**
     68 * @param array $args {
     69 *     Optional. An array of arguments.
     70 *
     71 *     @type string $post_type   Post type. Default 'post'.
     72 *     @type int    $post_author Post author ID.
     73 * }
     74 */
     75```
     76
     77PHPStan reads that hash as free text, so the value stays a plain `array` and nothing inside it is typed. `HashNotationVisitor` translates it into the array or object shape PHPStan understands, which for the example above is `array{post_type?: string, post_author?: int, ...}`, so the same documentation serves the reader and the analysis rather than each shape having to be written a second time as a `@phpstan-param`.
     78
     79A hash whose translation would be a guess is left alone, and the value keeps whatever type it has today. The visitor therefore only ever narrows a type, and never contradicts one:
     80
     81- A `@phpstan-param` or `@phpstan-return` written by hand always wins. Hash notation cannot express everything a type can — a function returning either of two shapes, or one whose return carries keys beyond those it documents, as `get_avatar_data()` returns the processed `$args` too — so a shape that has been tuned in the source is never overwritten by the derived one. Only the tag it was written for: a `@phpstan-param` for one parameter says nothing about the next, and a conditional `@phpstan-return` naming a parameter is not a shape for it.
     82- The declared type has to name something a shape can be put on: a bare `array` or `object`, or a class, on its own or as one member of a union such as `string|array` or `array|WP_Error` — in a union the bare member is the one the hash is about, since a `WP_Error` carries no keys. A type that is already more specific than the hash, such as `array<string, string|bool>`, is left as written.
     83- The hash has to be well formed: every `{` closed by a `}` on a line of its own, and every `@type` carrying a `$name` and a type that PHPStan's own type parser reads as one. Asking the parser rather than describing what a type may contain is what keeps `?string`, `(string|null)[]` and `callable(): void` from being turned away as malformed.
     84- A hash on a bare `object` that would have to stay open is skipped, because PHPStan's object shapes have a fixed member list and no `...`, and sealing one would report every member the hash does not name. A hash on a named class is kept: `stdClass&object{...}` is not sealed, since the intersection leaves the class to say what else may be read and a `stdClass` accepts anything. That is what lets the theme data `WP_Theme_Install_List_Table::single_row()` documents keep its shape while the wordpress.org API is still free to add to it.
     85- A parameter taken by reference is skipped, because PHPStan checks a by-reference argument in both directions, and a shape there would be a contract every caller's variable has to satisfy before the call rather than a description of what the function reads.
     86
     87Keys of a `@param` hash are optional, at every level, and the shape is left open with a trailing `...`, because the hash lists the keys core reads rather than the only keys a caller may pass. Keys of a `@return` hash are required and the shape is sealed, since they describe a value core itself builds. Numbered keys such as `$0` and `$1` are required in either case, because positional arguments are passed in order. A description opening with `Optional.` overrides all of that, and is the only thing that does: the word further into a description is prose, as in the "Optional self closing slash" of a match array whose every group is always set.
     88
     89Reading a key that a `@return` hash does not document is therefore reported rather than silently typed as `mixed` — unless the hash says otherwise itself, by listing a `...$N` entry beside its named keys, which is how core writes "and the rest" for the positional hashes of `wp_maybe_grant_site_health_caps()` and `WP_Meta_Query`.
     90
     91A hash on a class rather than on `array` or `object` produces an intersection, `stdClass&object{...}`, rather than a bare object shape. PHPStan's object shapes are structural, so a bare `object{...}` derived for a value core builds as a `stdClass` would no longer be assignable to a property declared `stdClass`. Intersecting keeps both: the value stays the class it is documented as, and its members are typed. This is why the returns that build one, such as `get_taxonomy_labels()`, document `stdClass` rather than `object`.
     92
     93One kind of hash is outside what the visitor covers today: **a `@var` hash on a property**. A property declaration is inherited by every subclass and has to accept its own default, so a shape there would say more than the hash does — that no subclass may widen the property, and that the declared default already has the shape.
     94
     95Hashes are also written on hook docblocks, where core documents `apply_filters()` and `do_action()`. Those are not attached to a function, so they are outside what this visitor sees, and the value a filter passes stays typed by [the hook extensions below](#hook-documentation).
    6196
    6297### Hook documentation
    … …  
    165200PHPStan caches analysis results to speed up subsequent runs. You can see information about the results cache by running `analyse` with the `-vv` or `-vvv` flag.
    166201
     202### Clear the cache after changing anything in this directory
     203
     204The `.cache` directory holds more than the results. PHPStan also stores what it read out of each source file there, the docblocks and signatures it found, keyed by that file's contents and nothing else. The sources in this directory change what reading a file yields without changing the file: `HashNotationVisitor` rewrites a docblock in the syntax tree, and the bytes on disk stay as they were.
     205
     206A cache written before one of them changed therefore answers with what the old code saw. The run does not fail or warn; it reports against types that are no longer derived, so a visitor can look as though it does nothing, or as though it does less than it does. So clear the cache by hand after editing anything here:
     207
     208```bash
     209rm -rf .cache
     210```
     211
     212The results cache alone is not the problem. PHPStan invalidates that itself when the configuration changes, and [`HookDocsResultCacheMetaExtension`](HookDocsResultCacheMetaExtension.php) already folds every file in this directory into its key, so it is discarded when one of them is edited. What survives either is the per-file reflection, which is keyed by the source file's own contents and has no way to know that reading it now yields something else. CI keys its cache on these files for the same reason; see [`.github/workflows/reusable-phpstan-static-analysis-v1.yml`](../../.github/workflows/reusable-phpstan-static-analysis-v1.yml).
     213
     214The same cache is worth clearing after analysing a subset of the tree. A file named on the command line is analysed, but a file merely *read* on its behalf is parsed without these extensions, and the reflection stored for it carries no derived shape. A later full run reads that back and reports against a type that is no longer what the docblock says, which is the same silence as above arriving from the other direction.
     215
    167216Sometimes, due to the lack of type information in legacy code, PHPStan may still struggle to analyze certain parts of the codebase. In such cases, you can use the `--debug` flag to disable caching and see which files are causing issues.
  • trunk/tests/phpstan/base.neon

    r63356 r63420  
    1010        -
    1111                class: WordPress\PHPStan\GlobalDocBlockVisitor
     12                tags:
     13                        - phpstan.parser.richParserNodeVisitor
     14
     15        # Bridges WordPress core's hash notation, the nested `@type` list documenting the
     16        # contents of an array or object, to PHPStan's array and object shapes.
     17        # See tests/phpstan/HashNotationVisitor.php.
     18        -
     19                class: WordPress\PHPStan\HashNotationVisitor
    1220                tags:
    1321                        - phpstan.parser.richParserNodeVisitor
    … …  
    158166                - ../../src/xmlrpc.php
    159167                - GlobalDocBlockVisitor.php
     168                - HashNotationVisitor.php
    160169                - HookDocsVisitor.php
    161170                - HookDocBlock.php
  • trunk/tests/phpstan/baselines/argument.type.neon

    r63352 r63420  
    365365                        path: ../../../src/wp-admin/includes/template.php
    366366                -
    367                         message: '#^Parameter \#1 \$update of method Language_Pack_Upgrader\:\:upgrade\(\) expects string\|false, stdClass given\.$#'
     367                        message: '#^Parameter \#1 \$update of method Language_Pack_Upgrader\:\:upgrade\(\) expects string\|false, object\{language\: string, version\: string, updated\: string, english_name\: string, native_name\: string, package\: string, iso\: array\<string\>, strings\: array\}&stdClass given\.$#'
    368368                        identifier: argument.type
    369369                        count: 1
    … …  
    500500                        path: ../../../src/wp-content/themes/twentyeleven/inc/theme-options.php
    501501                -
    502                         message: '#^Parameter \#3 \$args of function register_setting expects array, string given\.$#'
     502                        message: '#^Parameter \#3 \$args of function register_setting expects array\{type\?\: string, label\?\: string, description\?\: string, sanitize_callback\?\: \(callable\(\)\: mixed\)\|null, show_in_rest\?\: array\|bool, default\?\: mixed, \.\.\.\}, ''twentyeleven_theme…'' given\.$#'
    503503                        identifier: argument.type
    504504                        count: 1
    … …  
    805805                        path: ../../../src/wp-includes/class-wp-customize-manager.php
    806806                -
     807                        message: '#^Parameter \#1 \$args of method WP_Customize_Manager\:\:get_changeset_posts\(\) expects array\{posts_per_page\?\: int, author\?\: int, post_status\?\: string, exclude_restore_dismissed\?\: bool, \.\.\.\}, array\{post_status\: array\<non\-falsy\-string, non\-falsy\-string\>, exclude_restore_dismissed\: false, author\: ''any'', posts_per_page\: 1, order\: ''DESC'', orderby\: ''date''\} given\.$#'
     808                        identifier: argument.type
     809                        count: 1
     810                        path: ../../../src/wp-includes/class-wp-customize-manager.php
     811                -
    807812                        message: '#^Parameter \#1 \$month of function wp_checkdate expects int, \(string\|false\) given\.$#'
     813                        identifier: argument.type
     814                        count: 1
     815                        path: ../../../src/wp-includes/class-wp-customize-manager.php
     816                -
     817                        message: '#^Parameter \#1 \$postarr of function wp_insert_post expects array\{ID\?\: int, post_author\?\: int, post_date\?\: string, post_date_gmt\?\: string, post_content\?\: string, post_content_filtered\?\: string, post_title\?\: string, post_excerpt\?\: string, \.\.\., \.\.\.\}, array\<int\|string\|false\> given\.$#'
    808818                        identifier: argument.type
    809819                        count: 1
    … …  
    845855                        path: ../../../src/wp-includes/class-wp-duotone.php
    846856                -
     857                        message: '#^Parameter \#1 \$postarr of function wp_insert_post expects array\{ID\?\: int, post_author\?\: int, post_date\?\: string, post_date_gmt\?\: string, post_content\?\: string, post_content_filtered\?\: string, post_title\?\: string, post_excerpt\?\: string, \.\.\., \.\.\.\}, array\<string\> given\.$#'
     858                        identifier: argument.type
     859                        count: 2
     860                        path: ../../../src/wp-includes/class-wp-embed.php
     861                -
    847862                        message: '#^Parameter \#3 \$priority of function _wp_filter_build_unique_id expects int, false given\.$#'
    848863                        identifier: argument.type
    … …  
    940955                        path: ../../../src/wp-includes/class-wp-xmlrpc-server.php
    941956                -
     957                        message: '#^Parameter \#1 \$postarr of function wp_insert_post expects array\{ID\?\: int, post_author\?\: int, post_date\?\: string, post_date_gmt\?\: string, post_content\?\: string, post_content_filtered\?\: string, post_title\?\: string, post_excerpt\?\: string, \.\.\., \.\.\.\}, array\{post_author\: int, post_date\: int\|string, post_date_gmt\: int\|string, post_content\: string, post_title\: string, post_category\: array\<string\>\|string, post_status\: ''draft''\|''publish''\} given\.$#'
     958                        identifier: argument.type
     959                        count: 1
     960                        path: ../../../src/wp-includes/class-wp-xmlrpc-server.php
     961                -
    942962                        message: '#^Parameter \#1 \$term_id of method wp_xmlrpc_server\:\:get_term_custom_fields\(\) expects int, string given\.$#'
    943963                        identifier: argument.type
    … …  
    12351255                        path: ../../../src/wp-includes/nav-menu.php
    12361256                -
     1257                        message: '#^Parameter \#1 \$postarr of function wp_insert_post expects array\{ID\?\: int, post_author\?\: int, post_date\?\: string, post_date_gmt\?\: string, post_content\?\: string, post_content_filtered\?\: string, post_title\?\: string, post_excerpt\?\: string, \.\.\., \.\.\.\}, array\{menu_order\: mixed, ping_status\: 0, post_content\: mixed, post_excerpt\: mixed, post_parent\: int\|string\|WP_Error\|null, post_title\: mixed, post_type\: ''nav_menu_item'', post_date\?\: non\-falsy\-string, \.\.\.\} given\.$#'
     1258                        identifier: argument.type
     1259                        count: 1
     1260                        path: ../../../src/wp-includes/nav-menu.php
     1261                -
    12371262                        message: '#^Parameter \#2 \$value of function setcookie expects string, int\<1, max\> given\.$#'
    12381263                        identifier: argument.type
    … …  
    12751300                        path: ../../../src/wp-includes/pluggable.php
    12761301                -
     1302                        message: '#^Parameter \#1 \$args of function get_pages expects array\{child_of\?\: int, sort_order\?\: string, sort_column\?\: string, hierarchical\?\: bool, exclude\?\: array\<int\>, include\?\: array\<int\>, meta_key\?\: string, meta_value\?\: string, \.\.\., \.\.\.\}\|string, non\-empty\-array given\.$#'
     1303                        identifier: argument.type
     1304                        count: 1
     1305                        path: ../../../src/wp-includes/post-template.php
     1306                -
    12771307                        message: '#^Parameter \#1 \$attachment of function is_attachment expects array\<int\|string\>\|int\|string, WP_Post given\.$#'
    12781308                        identifier: argument.type
    … …  
    13821412                        message: '#^Parameter \#1 \$data_object of method WP_REST_Controller\:\:update_additional_fields_for_object\(\) expects object, array given\.$#'
    13831413                        identifier: argument.type
    1384                         count: 2
     1414                        count: 1
    13851415                        path: ../../../src/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php
    13861416                -
     1417                        message: '#^Parameter \#1 \$data_object of method WP_REST_Controller\:\:update_additional_fields_for_object\(\) expects object, array\<string, int\|string\|null\> given\.$#'
     1418                        identifier: argument.type
     1419                        count: 1
     1420                        path: ../../../src/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php
     1421                -
    13871422                        message: '#^Parameter \#1 \$comment_id of function get_comment_type expects int\|WP_Comment, string given\.$#'
    13881423                        identifier: argument.type
    … …  
    15751610                        path: ../../../src/wp-includes/user.php
    15761611                -
     1612                        message: '#^Parameter \#1 \$args of function register_sidebar expects array\{name\?\: string, id\?\: string, description\?\: string, class\?\: string, before_widget\?\: string, after_widget\?\: string, before_title\?\: string, after_title\?\: string, \.\.\., \.\.\.\}\|string, non\-empty\-array\<int\|string, array\<mixed\>\|string\> given\.$#'
     1613                        identifier: argument.type
     1614                        count: 1
     1615                        path: ../../../src/wp-includes/widgets.php
     1616                -
    15771617                        message: '#^Parameter \#3 \$control_callback of function wp_register_widget_control expects callable\(\)\: mixed, '''' given\.$#'
    15781618                        identifier: argument.type
  • trunk/tests/phpstan/baselines/assign.propertyType.neon

    r63404 r63420  
    145145                        path: ../../../src/wp-includes/taxonomy.php
    146146                -
    147                         message: '#^Static property WP_Widget_Media\:\:\$l10n_defaults \(array\<string\>\) does not accept array\<string, array\|string\>\.$#'
     147                        message: '#^Static property WP_Widget_Media\:\:\$l10n_defaults \(array\<string\>\) does not accept array\<string, array\<int\|string, string\|null\>\|string\>\.$#'
    148148                        identifier: assign.propertyType
    149149                        count: 1
  • trunk/tests/phpstan/baselines/offsetAccess.notFound.neon

    r63022 r63420  
    2020        ignoreErrors:
    2121                -
    22                         message: '#^Offset float does not exist on list\.$#'
     22                        message: '#^Offset float does not exist on list\<float\>\.$#'
    2323                        identifier: offsetAccess.notFound
    2424                        count: 1
Note: See TracChangeset for help on using the changeset viewer.