Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
99c6fcb
Ensure wp_parse_list() returns list<scalar>
westonruter Jul 18, 2026
3c8203c
Type wp_parse_id_list() return as list<non-negative-int>
westonruter Jul 18, 2026
5175858
Fix remaining PHPStan issues with wp_parse_list()
westonruter Jul 19, 2026
975f570
Ensure wp_parse_id_list() returns a list
westonruter Jul 19, 2026
756bf6a
Fix phpstan complaint about under-typed array param
westonruter Jul 19, 2026
d86624d
Explicitly cast values to strings before passing into sanitize_title()
westonruter Jul 19, 2026
91074b9
Indicate that returned values may include zero and empty string
westonruter Jul 19, 2026
e6ae606
Add wp_parse_list() tests for non-scalars and list return value
westonruter Jul 19, 2026
c14e8fd
Improve Tests_Functions_wpParseIdList
westonruter Jul 19, 2026
e7c8fe0
Use assertSame instead of assertSameSets
westonruter Jul 19, 2026
6900fc5
Fix type of list
westonruter Jul 19, 2026
0838a96
Update tests for wp_parse_slug_list()
westonruter Jul 19, 2026
3fbcfea
Add tests for assoc arrays being made into lists
westonruter Jul 19, 2026
70db2cf
Abandon coercing to lists
westonruter Jul 19, 2026
150c52d
Avoid casting preg_split() failure to array( false )
westonruter Jul 19, 2026
109089d
Correct copy-pasted assertion message in wp_parse_slug_list() test
westonruter Jul 19, 2026
c4ed42f
Assert the scalar return contract in the wp_parse_list() test
westonruter Jul 19, 2026
04257fb
Document why the parsed lists are not necessarily PHP lists
westonruter Jul 19, 2026
c92aa4a
Narrow wp_parse_list() to a list when a string is passed
westonruter Jul 19, 2026
c493b6f
State the string-yields-a-list guarantee in the prose return description
westonruter Jul 19, 2026
5b0c488
Reduce line length
westonruter Jul 19, 2026
28e5661
Merge branch 'trunk' into update/parse-list-typing
westonruter Jul 19, 2026
03c79b0
Add return type hints
westonruter Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5021,12 +5021,15 @@ function wp_parse_args( $args, $defaults = array() ) {
*
* @since 5.1.0
*
* @param array|string $input_list List of values.
* @return array Array of values.
* @param mixed[]|string $input_list List of values.
* @return array Array of values. A string is split into a list, while an array
* keeps its keys, so the result is not necessarily a list.
* @phpstan-return ($input_list is string ? list<string> : array<scalar>)
*/
function wp_parse_list( $input_list ) {
function wp_parse_list( $input_list ): array {
if ( ! is_array( $input_list ) ) {
return preg_split( '/[\s,]+/', $input_list, -1, PREG_SPLIT_NO_EMPTY );
$parsed_list = preg_split( '/[\s,]+/', $input_list, -1, PREG_SPLIT_NO_EMPTY );
return is_array( $parsed_list ) ? $parsed_list : array();
}

// Validate all entries of the list are scalar.
Expand All @@ -5041,10 +5044,13 @@ function wp_parse_list( $input_list ) {
* @since 3.0.0
* @since 5.1.0 Refactored to use wp_parse_list().
*
* @param array|string $input_list List of IDs.
* @return int[] Sanitized array of IDs.
* @param mixed[]|string $input_list List of IDs.
* @return int[] Sanitized array of IDs. May include zero. Keys are preserved
* from the input and `array_unique()` may leave gaps, so the
* result is not necessarily a list.
* @phpstan-return array<non-negative-int>
*/
function wp_parse_id_list( $input_list ) {
function wp_parse_id_list( $input_list ): array {
$input_list = wp_parse_list( $input_list );

return array_unique( array_map( 'absint', $input_list ) );
Expand All @@ -5056,13 +5062,27 @@ function wp_parse_id_list( $input_list ) {
* @since 4.7.0
* @since 5.1.0 Refactored to use wp_parse_list().
*
* @param array|string $input_list List of slugs.
* @return string[] Sanitized array of slugs.
* @param mixed[]|string $input_list List of slugs.
* @return string[] Sanitized array of slugs. May include an empty string. Keys
* are preserved from the input and `array_unique()` may leave
* gaps, so the result is not necessarily a list.
*/
function wp_parse_slug_list( $input_list ) {
function wp_parse_slug_list( $input_list ): array {
$input_list = wp_parse_list( $input_list );

return array_unique( array_map( 'sanitize_title', $input_list ) );
return array_unique(
array_map(
'sanitize_title',
array_map(
/*
* Cast booleans, integers, and floats to strings. Non-scalar types
* (including null) have already been filtered out by wp_parse_list().
*/
'strval',
$input_list
)
)
);
}

/**
Expand Down
52 changes: 45 additions & 7 deletions tests/phpunit/tests/functions/wpParseIdList.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,31 @@ class Tests_Functions_wpParseIdList extends WP_UnitTestCase {
*
* @dataProvider data_wp_parse_id_list
* @dataProvider data_unexpected_input
*
* @param mixed[]|string $input_list
* @param array<non-negative-int> $expected
*/
public function test_wp_parse_id_list( $input_list, $expected ) {
$this->assertSameSets( $expected, wp_parse_id_list( $input_list ) );
public function test_wp_parse_id_list( $input_list, array $expected ): void {
$parsed_list = wp_parse_id_list( $input_list );
$this->assertThat(
$parsed_list,
$this->callback(
static fn ( array $arr ) => array_all(
$arr,
static fn ( $v ) => is_int( $v ) && $v >= 0
)
),
'Array should contain only non-negative ints.'
);
$this->assertSame( $expected, $parsed_list );
}

/**
* Data provider.
*
* @return array[]
* @return array<string, array{ input_list: mixed[]|string, expected: array<non-negative-int> }>
*/
public function data_wp_parse_id_list() {
public function data_wp_parse_id_list(): array {
return array(
'regular' => array(
'input_list' => '1,2,3,4',
Expand All @@ -37,7 +51,12 @@ public function data_wp_parse_id_list() {
),
'duplicate id in a string' => array(
'input_list' => '1,2,2,3,4',
'expected' => array( 1, 2, 3, 4 ),
'expected' => array(
0 => 1,
1 => 2,
3 => 3,
4 => 4,
),
),
'duplicate id in an array' => array(
'input_list' => array( '1', '2', '3', '4', '3' ),
Expand All @@ -61,9 +80,9 @@ public function data_wp_parse_id_list() {
/**
* Data provider.
*
* @return array[]
* @return array<string, array{ input_list: mixed[]|string, expected: array<non-negative-int> }>
*/
public function data_unexpected_input() {
public function data_unexpected_input(): array {
return array(
'string with commas' => array(
'input_list' => '1,2,string with spaces',
Expand Down Expand Up @@ -97,6 +116,25 @@ public function data_unexpected_input() {
'input_list' => array( 1, 2, false ),
'expected' => array( 1, 2, 0 ),
),
'array with array' => array(
'input_list' => array( 1, array(), 2 ),
'expected' => array(
0 => 1,
2 => 2,
),
),
'passed assoc array' => array(
'input_list' => array(
'one' => 1,
'two' => '2',
'three' => '3 is company',
),
'expected' => array(
'one' => 1,
'two' => 2,
'three' => 3,
),
),
);
}
}
70 changes: 55 additions & 15 deletions tests/phpunit/tests/functions/wpParseList.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,102 @@ class Tests_Functions_wpParseList extends WP_UnitTestCase {
* @ticket 43977
*
* @dataProvider data_wp_parse_list
*
* @param mixed[]|string $input_list
* @param array<scalar> $expected
*/
public function test_wp_parse_list( $input_list, $expected ) {
$this->assertSameSets( $expected, wp_parse_list( $input_list ) );
public function test_wp_parse_list( $input_list, array $expected ): void {
$parsed_list = wp_parse_list( $input_list );
$this->assertThat(
$parsed_list,
$this->callback(
static fn ( array $arr ) => array_all(
$arr,
static fn ( $v ) => is_scalar( $v )
)
),
'Array should contain only scalars.'
);
$this->assertSame( $expected, $parsed_list );
}

/**
* Data provider.
*
* @return array[]
* @return array<string, array{ input_list: mixed[]|string, expected: array<scalar> }>
*/
public function data_wp_parse_list() {
public function data_wp_parse_list(): array {
return array(
'ids only' => array(
'ids only' => array(
'input_list' => '1,2,3,4',
'expected' => array( '1', '2', '3', '4' ),
),
'slugs only' => array(
'slugs only' => array(
'input_list' => 'apple,banana,carrot,dog',
'expected' => array( 'apple', 'banana', 'carrot', 'dog' ),
),
'ids and slugs' => array(
'ids and slugs' => array(
'input_list' => '1,2,apple,banana',
'expected' => array( '1', '2', 'apple', 'banana' ),
),
'space after comma' => array(
'space after comma' => array(
'input_list' => '1, 2,apple,banana',
'expected' => array( '1', '2', 'apple', 'banana' ),
),
'double comma' => array(
'double comma' => array(
'input_list' => '1,2,apple,,banana',
'expected' => array( '1', '2', 'apple', 'banana' ),
),
'leading comma' => array(
'leading comma' => array(
'input_list' => ',1,2,apple,banana',
'expected' => array( '1', '2', 'apple', 'banana' ),
),
'trailing comma' => array(
'trailing comma' => array(
'input_list' => '1,2,apple,banana,',
'expected' => array( '1', '2', 'apple', 'banana' ),
),
'space before comma' => array(
'space before comma' => array(
'input_list' => '1,2 ,apple,banana',
'expected' => array( '1', '2', 'apple', 'banana' ),
),
'empty string' => array(
'empty string' => array(
'input_list' => '',
'expected' => array(),
),
'comma only' => array(
'comma only' => array(
'input_list' => ',',
'expected' => array(),
),
'double comma only' => array(
'double comma only' => array(
'input_list' => ',,',
'expected' => array(),
),
'passed scalar array' => array(
'input_list' => array( 'foo', true, false, 1, 3.14 ),
'expected' => array( 'foo', true, false, 1, 3.14 ),
),
'passed mixed array' => array(
'input_list' => array( null, 'foo', array(), true, new stdClass(), false, 1, 3.14 ),
'expected' => array(
1 => 'foo',
3 => true,
5 => false,
6 => 1,
7 => 3.14,
),
),
'passed assoc array' => array(
'input_list' => array(
'foo' => 1,
'bar' => true,
'baz' => 3.14,
),
'expected' => array(
'foo' => 1,
'bar' => true,
'baz' => 3.14,
),
),
);
}
}
63 changes: 55 additions & 8 deletions tests/phpunit/tests/functions/wpParseSlugList.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,31 @@ class Tests_Functions_WpParseSlugList extends WP_UnitTestCase {
*
* @dataProvider data_wp_parse_slug_list
* @dataProvider data_unexpected_input
*
* @param mixed[]|string $input_list
* @param array<string> $expected
*/
public function test_wp_parse_slug_list( $input_list, $expected ) {
$this->assertSameSets( $expected, wp_parse_slug_list( $input_list ) );
public function test_wp_parse_slug_list( $input_list, array $expected ): void {
$parsed_list = wp_parse_slug_list( $input_list );
$this->assertThat(
$parsed_list,
$this->callback(
static fn ( array $arr ) => array_all(
$arr,
static fn ( $v ) => is_string( $v )
)
),
'Array should contain only strings.'
);
$this->assertSame( $expected, $parsed_list );
}

/**
* Data provider.
*
* @return array[]
* @return array<string, array{ input_list: mixed[]|string, expected: array<string> }>
*/
public function data_wp_parse_slug_list() {
public function data_wp_parse_slug_list(): array {
return array(
'regular' => array(
'input_list' => 'apple,banana,carrot,dog',
Expand All @@ -37,11 +51,21 @@ public function data_wp_parse_slug_list() {
),
'duplicate slug in a string' => array(
'input_list' => 'apple,banana,carrot,carrot,dog',
'expected' => array( 'apple', 'banana', 'carrot', 'dog' ),
'expected' => array(
0 => 'apple',
1 => 'banana',
2 => 'carrot',
4 => 'dog',
),
),
'duplicate slug in an array' => array(
'input_list' => array( 'apple', 'banana', 'carrot', 'carrot', 'dog' ),
'expected' => array( 'apple', 'banana', 'carrot', 'dog' ),
'expected' => array(
0 => 'apple',
1 => 'banana',
2 => 'carrot',
4 => 'dog',
),
),
'string with spaces' => array(
'input_list' => 'apple banana carrot dog',
Expand All @@ -51,15 +75,27 @@ public function data_wp_parse_slug_list() {
'input_list' => array( 'apple ', 'banana carrot', 'd o g' ),
'expected' => array( 'apple', 'banana-carrot', 'd-o-g' ),
),
'passed assoc array' => array(
'input_list' => array(
'one' => 'foo',
'two' => 'bar',
'three' => 'baz',
),
'expected' => array(
'one' => 'foo',
'two' => 'bar',
'three' => 'baz',
),
),
);
}

/**
* Data provider.
*
* @return array[]
* @return array<string, array{ input_list: mixed[]|string, expected: array<string> }>
*/
public function data_unexpected_input() {
public function data_unexpected_input(): array {
return array(
'string with commas' => array(
'input_list' => '1,2,string with spaces',
Expand Down Expand Up @@ -93,6 +129,17 @@ public function data_unexpected_input() {
'input_list' => array( 1, 2, false ),
'expected' => array( '1', '2', '' ),
),
'array with array' => array(
'input_list' => array( 1, array(), 2 ),
'expected' => array(
0 => '1',
2 => '2',
),
),
'array with tag' => array(
'input_list' => array( 1, '<br>', 2 ),
'expected' => array( '1', '', '2' ),
),
);
}
}
Loading