Make WordPress Core

Changeset 62797


Ignore:
Timestamp:
07/19/2026 04:53:46 PM (3 months ago)
Author:
westonruter
Message:

Code Quality: Improve typing for wp_parse_list() et al.

This adds precise @param and @return typing for the wp_parse_list(), wp_parse_id_list(), and wp_parse_slug_list() functions; native array return types are also added. It also adds casting and type guarding to guarantee the types of the values involved. Descriptions are updated to indicate that lists may not be returned, which may be unexpected given the function names; instead, sparse arrays or even associative arrays may be returned. Additionally, typically invalid ID values like zero may be in the array returned by wp_parse_id_list() and an empty string may be in the array returned by wp_parse_slug_list().

Tests are added to ensure existing behavior is preserved. This fixes 6 PHPStan errors at rule level 10.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/12588.
Follow-up to r38832, r44546, r57737, r62647, r62771.

See #64898.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/functions.php

    r62704 r62797  
    50225022 * @since 5.1.0
    50235023 *
    5024  * @param array|string $input_list List of values.
    5025  * @return array Array of values.
    5026  */
    5027 function wp_parse_list( $input_list ) {
     5024 * @param mixed[]|string $input_list List of values.
     5025 * @return array Array of values. A string is split into a list, while an array
     5026 *               keeps its keys, so the result is not necessarily a list.
     5027 * @phpstan-return ( $input_list is string ? list<string> : array<scalar> )
     5028 */
     5029function wp_parse_list( $input_list ): array {
    50285030        if ( ! is_array( $input_list ) ) {
    5029                 return preg_split( '/[\s,]+/', $input_list, -1, PREG_SPLIT_NO_EMPTY );
     5031                $parsed_list = preg_split( '/[\s,]+/', $input_list, -1, PREG_SPLIT_NO_EMPTY );
     5032                return is_array( $parsed_list ) ? $parsed_list : array();
    50305033        }
    50315034
    … …  
    50425045 * @since 5.1.0 Refactored to use wp_parse_list().
    50435046 *
    5044  * @param array|string $input_list List of IDs.
    5045  * @return int[] Sanitized array of IDs.
    5046  */
    5047 function wp_parse_id_list( $input_list ) {
     5047 * @param mixed[]|string $input_list List of IDs.
     5048 * @return int[] Sanitized array of IDs. May include zero. Keys are preserved
     5049 *               from the input and `array_unique()` may leave gaps, so the
     5050 *               result is not necessarily a list.
     5051 * @phpstan-return array<non-negative-int>
     5052 */
     5053function wp_parse_id_list( $input_list ): array {
    50485054        $input_list = wp_parse_list( $input_list );
    50495055
    … …  
    50575063 * @since 5.1.0 Refactored to use wp_parse_list().
    50585064 *
    5059  * @param array|string $input_list List of slugs.
    5060  * @return string[] Sanitized array of slugs.
    5061  */
    5062 function wp_parse_slug_list( $input_list ) {
     5065 * @param mixed[]|string $input_list List of slugs.
     5066 * @return string[] Sanitized array of slugs. May include an empty string. Keys
     5067 *                  are preserved from the input and `array_unique()` may leave
     5068 *                  gaps, so the result is not necessarily a list.
     5069 */
     5070function wp_parse_slug_list( $input_list ): array {
    50635071        $input_list = wp_parse_list( $input_list );
    50645072
    5065         return array_unique( array_map( 'sanitize_title', $input_list ) );
     5073        return array_unique(
     5074                array_map(
     5075                        'sanitize_title',
     5076                        array_map(
     5077                                /*
     5078                                 * Cast booleans, integers, and floats to strings. Non-scalar types
     5079                                 * (including null) have already been filtered out by wp_parse_list().
     5080                                 */
     5081                                'strval',
     5082                                $input_list
     5083                        )
     5084                )
     5085        );
    50665086}
    50675087
  • trunk/tests/phpunit/tests/functions/wpParseIdList.php

    r57737 r62797  
    1616         * @dataProvider data_wp_parse_id_list
    1717         * @dataProvider data_unexpected_input
     18         *
     19         * @param mixed[]|string $input_list
     20         * @param array<non-negative-int> $expected
    1821         */
    19         public function test_wp_parse_id_list( $input_list, $expected ) {
    20                 $this->assertSameSets( $expected, wp_parse_id_list( $input_list ) );
     22        public function test_wp_parse_id_list( $input_list, array $expected ): void {
     23                $parsed_list = wp_parse_id_list( $input_list );
     24                $this->assertThat(
     25                        $parsed_list,
     26                        $this->callback(
     27                                static fn ( array $arr ) => array_all(
     28                                        $arr,
     29                                        static fn ( $v ) => is_int( $v ) && $v >= 0
     30                                )
     31                        ),
     32                        'Array should contain only non-negative ints.'
     33                );
     34                $this->assertSame( $expected, $parsed_list );
    2135        }
    2236
    … …  
    2438         * Data provider.
    2539         *
    26          * @return array[]
     40         * @return array<string, array{ input_list: mixed[]|string, expected: array<non-negative-int> }>
    2741         */
    28         public function data_wp_parse_id_list() {
     42        public function data_wp_parse_id_list(): array {
    2943                return array(
    3044                        'regular'                  => array(
    … …  
    3852                        'duplicate id in a string' => array(
    3953                                'input_list' => '1,2,2,3,4',
    40                                 'expected'   => array( 1, 2, 3, 4 ),
     54                                'expected'   => array(
     55                                        0 => 1,
     56                                        1 => 2,
     57                                        3 => 3,
     58                                        4 => 4,
     59                                ),
    4160                        ),
    4261                        'duplicate id in an array' => array(
    … …  
    6281         * Data provider.
    6382         *
    64          * @return array[]
     83         * @return array<string, array{ input_list: mixed[]|string, expected: array<non-negative-int> }>
    6584         */
    66         public function data_unexpected_input() {
     85        public function data_unexpected_input(): array {
    6786                return array(
    6887                        'string with commas' => array(
    … …  
    98117                                'expected'   => array( 1, 2, 0 ),
    99118                        ),
     119                        'array with array'   => array(
     120                                'input_list' => array( 1, array(), 2 ),
     121                                'expected'   => array(
     122                                        0 => 1,
     123                                        2 => 2,
     124                                ),
     125                        ),
     126                        'passed assoc array' => array(
     127                                'input_list' => array(
     128                                        'one'   => 1,
     129                                        'two'   => '2',
     130                                        'three' => '3 is company',
     131                                ),
     132                                'expected'   => array(
     133                                        'one'   => 1,
     134                                        'two'   => 2,
     135                                        'three' => 3,
     136                                ),
     137                        ),
    100138                );
    101139        }
  • trunk/tests/phpunit/tests/functions/wpParseList.php

    r57737 r62797  
    1414         *
    1515         * @dataProvider data_wp_parse_list
     16         *
     17         * @param mixed[]|string $input_list
     18         * @param array<scalar> $expected
    1619         */
    17         public function test_wp_parse_list( $input_list, $expected ) {
    18                 $this->assertSameSets( $expected, wp_parse_list( $input_list ) );
     20        public function test_wp_parse_list( $input_list, array $expected ): void {
     21                $parsed_list = wp_parse_list( $input_list );
     22                $this->assertThat(
     23                        $parsed_list,
     24                        $this->callback(
     25                                static fn ( array $arr ) => array_all(
     26                                        $arr,
     27                                        static fn ( $v ) => is_scalar( $v )
     28                                )
     29                        ),
     30                        'Array should contain only scalars.'
     31                );
     32                $this->assertSame( $expected, $parsed_list );
    1933        }
    2034
    … …  
    2236         * Data provider.
    2337         *
    24          * @return array[]
     38         * @return array<string, array{ input_list: mixed[]|string, expected: array<scalar> }>
    2539         */
    26         public function data_wp_parse_list() {
     40        public function data_wp_parse_list(): array {
    2741                return array(
    28                         'ids only'           => array(
     42                        'ids only'            => array(
    2943                                'input_list' => '1,2,3,4',
    3044                                'expected'   => array( '1', '2', '3', '4' ),
    3145                        ),
    32                         'slugs only'         => array(
     46                        'slugs only'          => array(
    3347                                'input_list' => 'apple,banana,carrot,dog',
    3448                                'expected'   => array( 'apple', 'banana', 'carrot', 'dog' ),
    3549                        ),
    36                         'ids and slugs'      => array(
     50                        'ids and slugs'       => array(
    3751                                'input_list' => '1,2,apple,banana',
    3852                                'expected'   => array( '1', '2', 'apple', 'banana' ),
    3953                        ),
    40                         'space after comma'  => array(
     54                        'space after comma'   => array(
    4155                                'input_list' => '1, 2,apple,banana',
    4256                                'expected'   => array( '1', '2', 'apple', 'banana' ),
    4357                        ),
    44                         'double comma'       => array(
     58                        'double comma'        => array(
    4559                                'input_list' => '1,2,apple,,banana',
    4660                                'expected'   => array( '1', '2', 'apple', 'banana' ),
    4761                        ),
    48                         'leading comma'      => array(
     62                        'leading comma'       => array(
    4963                                'input_list' => ',1,2,apple,banana',
    5064                                'expected'   => array( '1', '2', 'apple', 'banana' ),
    5165                        ),
    52                         'trailing comma'     => array(
     66                        'trailing comma'      => array(
    5367                                'input_list' => '1,2,apple,banana,',
    5468                                'expected'   => array( '1', '2', 'apple', 'banana' ),
    5569                        ),
    56                         'space before comma' => array(
     70                        'space before comma'  => array(
    5771                                'input_list' => '1,2 ,apple,banana',
    5872                                'expected'   => array( '1', '2', 'apple', 'banana' ),
    5973                        ),
    60                         'empty string'       => array(
     74                        'empty string'        => array(
    6175                                'input_list' => '',
    6276                                'expected'   => array(),
    6377                        ),
    64                         'comma only'         => array(
     78                        'comma only'          => array(
    6579                                'input_list' => ',',
    6680                                'expected'   => array(),
    6781                        ),
    68                         'double comma only'  => array(
     82                        'double comma only'   => array(
    6983                                'input_list' => ',,',
    7084                                'expected'   => array(),
     85                        ),
     86                        'passed scalar array' => array(
     87                                'input_list' => array( 'foo', true, false, 1, 3.14 ),
     88                                'expected'   => array( 'foo', true, false, 1, 3.14 ),
     89                        ),
     90                        'passed mixed array'  => array(
     91                                'input_list' => array( null, 'foo', array(), true, new stdClass(), false, 1, 3.14 ),
     92                                'expected'   => array(
     93                                        1 => 'foo',
     94                                        3 => true,
     95                                        5 => false,
     96                                        6 => 1,
     97                                        7 => 3.14,
     98                                ),
     99                        ),
     100                        'passed assoc array'  => array(
     101                                'input_list' => array(
     102                                        'foo' => 1,
     103                                        'bar' => true,
     104                                        'baz' => 3.14,
     105                                ),
     106                                'expected'   => array(
     107                                        'foo' => 1,
     108                                        'bar' => true,
     109                                        'baz' => 3.14,
     110                                ),
    71111                        ),
    72112                );
  • trunk/tests/phpunit/tests/functions/wpParseSlugList.php

    r57737 r62797  
    1616         * @dataProvider data_wp_parse_slug_list
    1717         * @dataProvider data_unexpected_input
     18         *
     19         * @param mixed[]|string $input_list
     20         * @param array<string> $expected
    1821         */
    19         public function test_wp_parse_slug_list( $input_list, $expected ) {
    20                 $this->assertSameSets( $expected, wp_parse_slug_list( $input_list ) );
     22        public function test_wp_parse_slug_list( $input_list, array $expected ): void {
     23                $parsed_list = wp_parse_slug_list( $input_list );
     24                $this->assertThat(
     25                        $parsed_list,
     26                        $this->callback(
     27                                static fn ( array $arr ) => array_all(
     28                                        $arr,
     29                                        static fn ( $v ) => is_string( $v )
     30                                )
     31                        ),
     32                        'Array should contain only strings.'
     33                );
     34                $this->assertSame( $expected, $parsed_list );
    2135        }
    2236
    … …  
    2438         * Data provider.
    2539         *
    26          * @return array[]
     40         * @return array<string, array{ input_list: mixed[]|string, expected: array<string> }>
    2741         */
    28         public function data_wp_parse_slug_list() {
     42        public function data_wp_parse_slug_list(): array {
    2943                return array(
    3044                        'regular'                    => array(
    … …  
    3852                        'duplicate slug in a string' => array(
    3953                                'input_list' => 'apple,banana,carrot,carrot,dog',
    40                                 'expected'   => array( 'apple', 'banana', 'carrot', 'dog' ),
     54                                'expected'   => array(
     55                                        0 => 'apple',
     56                                        1 => 'banana',
     57                                        2 => 'carrot',
     58                                        4 => 'dog',
     59                                ),
    4160                        ),
    4261                        'duplicate slug in an array' => array(
    4362                                'input_list' => array( 'apple', 'banana', 'carrot', 'carrot', 'dog' ),
    44                                 'expected'   => array( 'apple', 'banana', 'carrot', 'dog' ),
     63                                'expected'   => array(
     64                                        0 => 'apple',
     65                                        1 => 'banana',
     66                                        2 => 'carrot',
     67                                        4 => 'dog',
     68                                ),
    4569                        ),
    4670                        'string with spaces'         => array(
    … …  
    5276                                'expected'   => array( 'apple', 'banana-carrot', 'd-o-g' ),
    5377                        ),
     78                        'passed assoc array'         => array(
     79                                'input_list' => array(
     80                                        'one'   => 'foo',
     81                                        'two'   => 'bar',
     82                                        'three' => 'baz',
     83                                ),
     84                                'expected'   => array(
     85                                        'one'   => 'foo',
     86                                        'two'   => 'bar',
     87                                        'three' => 'baz',
     88                                ),
     89                        ),
    5490                );
    5591        }
    … …  
    5894         * Data provider.
    5995         *
    60          * @return array[]
     96         * @return array<string, array{ input_list: mixed[]|string, expected: array<string> }>
    6197         */
    62         public function data_unexpected_input() {
     98        public function data_unexpected_input(): array {
    6399                return array(
    64100                        'string with commas' => array(
    … …  
    94130                                'expected'   => array( '1', '2', '' ),
    95131                        ),
     132                        'array with array'   => array(
     133                                'input_list' => array( 1, array(), 2 ),
     134                                'expected'   => array(
     135                                        0 => '1',
     136                                        2 => '2',
     137                                ),
     138                        ),
     139                        'array with tag'     => array(
     140                                'input_list' => array( 1, '<br>', 2 ),
     141                                'expected'   => array( '1', '', '2' ),
     142                        ),
    96143                );
    97144        }
Note: See TracChangeset for help on using the changeset viewer.