From f103c05503e1a29555cde2a321b0e1aff0906e5a Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Wed, 8 Nov 2023 23:29:22 +0100 Subject: [PATCH 1/8] Exclude "must be overriden" from adding return never --- visitor.php | 6 ++++++ wordpress-stubs.php | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/visitor.php b/visitor.php index b07a08f..c82ecbd 100644 --- a/visitor.php +++ b/visitor.php @@ -26,6 +26,7 @@ use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Return_ as Stmt_Return; +use PhpParser\NodeDumper; use StubsGenerator\NodeVisitor; abstract class WithChildren @@ -1050,6 +1051,11 @@ static function (Node $node): bool { } // If a first level statement is exit/die, it's return type never. if ($stmt->expr instanceof Exit_) { + if ($stmt->expr->expr instanceof String_) { + if (strpos($stmt->expr->expr->value, 'must be overridden') !== false) { + return ''; + } + } return 'never'; } if (!($stmt->expr instanceof FuncCall) || !($stmt->expr->name instanceof Name)) { diff --git a/wordpress-stubs.php b/wordpress-stubs.php index d231c4f..c5b6414 100644 --- a/wordpress-stubs.php +++ b/wordpress-stubs.php @@ -4312,7 +4312,6 @@ public function __call($name, $arguments) * * @since 3.1.0 * @abstract - * @phpstan-return never */ public function ajax_user_can() { @@ -4324,7 +4323,6 @@ public function ajax_user_can() * * @since 3.1.0 * @abstract - * @phpstan-return never */ public function prepare_items() { @@ -4571,7 +4569,6 @@ protected function pagination($which) * @abstract * * @return array - * @phpstan-return never */ public function get_columns() { @@ -54724,7 +54721,6 @@ class WP_Widget * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance The settings for the particular instance of the widget. - * @phpstan-return never */ public function widget($args, $instance) { From f456e6552d2e6b405217e74ce0576eec7ffca05a Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Wed, 8 Nov 2023 23:34:24 +0100 Subject: [PATCH 2/8] Remove NodeDumper --- visitor.php | 1 - 1 file changed, 1 deletion(-) diff --git a/visitor.php b/visitor.php index c82ecbd..72c48f9 100644 --- a/visitor.php +++ b/visitor.php @@ -26,7 +26,6 @@ use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Return_ as Stmt_Return; -use PhpParser\NodeDumper; use StubsGenerator\NodeVisitor; abstract class WithChildren From 9b4fee30418e5998dea3277a1ee7f4790b744935 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:26:40 +0100 Subject: [PATCH 3/8] Add conditional return type for $args array of get_posts --- functionMap.php | 1 + tests/TypeInferenceTest.php | 1 + tests/data/get_posts.php | 64 +++++++++++++++++++++++++++++++++++++ wordpress-stubs.php | 1 + 4 files changed, 67 insertions(+) create mode 100644 tests/data/get_posts.php diff --git a/functionMap.php b/functionMap.php index ed09859..1642088 100644 --- a/functionMap.php +++ b/functionMap.php @@ -22,6 +22,7 @@ * @link https://github.com/phpstan/phpstan-src/blob/1.10.x/resources/functionMap.php */ return [ + 'get_posts' => ["(\$args is array{'fields': 'id=>parent'}|array{'fields': 'ids'} ? array : array)"], 'addslashes_gpc' => ['T', '@phpstan-template' => 'T', 'gpc' => 'T'], 'have_posts' => [null, '@phpstan-impure' => ''], 'rawurlencode_deep' => ['T', '@phpstan-template' => 'T', 'value' => 'T'], diff --git a/tests/TypeInferenceTest.php b/tests/TypeInferenceTest.php index 267dea3..19c7a8e 100644 --- a/tests/TypeInferenceTest.php +++ b/tests/TypeInferenceTest.php @@ -20,6 +20,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post_stati.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post_types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/get_posts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_page_by_path.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_permalink.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_term_by.php'); diff --git a/tests/data/get_posts.php b/tests/data/get_posts.php new file mode 100644 index 0000000..e6c8d6a --- /dev/null +++ b/tests/data/get_posts.php @@ -0,0 +1,64 @@ +', get_posts()); +assertType('array', get_posts(['key' => 'value'])); +assertType('array', get_posts(['fields' => ''])); +assertType('array', get_posts(['fields' => 'ids'])); +assertType('array', get_posts(['fields' => 'id=>parent'])); +assertType('array', get_posts(['fields' => 'Hello'])); + +// Nonconstant array +assertType('array', get_posts((array)$_GET['array'])); + +// Unions +$union = $_GET['foo'] ? ['key' => 'value'] : ['some' => 'thing']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? ['key' => 'value'] : ['fields' => 'ids']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? ['key' => 'value'] : ['fields' => '']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? ['key' => 'value'] : ['fields' => 'id=>parent']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? ['fields' => ''] : ['fields' => 'ids']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? ['fields' => ''] : ['fields' => 'id=>parent']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? ['fields' => 'ids'] : ['fields' => 'id=>parent']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? (array)$_GET['array'] : ['fields' => '']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? (array)$_GET['array'] : ['fields' => 'ids']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? (array)$_GET['array'] : ['fields' => 'id=>parent']; +assertType('array', get_posts($union)); + +$union = $_GET['foo'] ? (string)$_GET['string'] : ''; +assertType('array', get_posts(['fields' => $union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'ids'; +assertType('array', get_posts(['fields' => $union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'id=>parent'; +assertType('array', get_posts(['fields' => $union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array', get_posts([$union => ''])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array', get_posts([$union => 'ids'])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array', get_posts([$union => 'id=>parent'])); diff --git a/wordpress-stubs.php b/wordpress-stubs.php index 26eee83..c569497 100644 --- a/wordpress-stubs.php +++ b/wordpress-stubs.php @@ -126600,6 +126600,7 @@ function is_post_publicly_viewable($post = \null) * w?: int, * year?: int, * } $args + * @phpstan-return ($args is array{'fields': 'id=>parent'}|array{'fields': 'ids'} ? array : array) */ function get_posts($args = \null) { From ba9330555d556bd4ee0f60e4f7cf3d32fd0af78f Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:19:23 +0100 Subject: [PATCH 4/8] Add missing `use` statement --- visitor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/visitor.php b/visitor.php index ade68a9..648ee05 100644 --- a/visitor.php +++ b/visitor.php @@ -18,6 +18,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Expr\Exit_; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; From ea5b20cd1576dace1d74c87467b3bd7fc6c0b3fa Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:22:51 +0100 Subject: [PATCH 5/8] Fix some cs issues and remove visitor.php from phpcs.xml.dist --- finder.php | 5 +++-- functionMap.php | 10 ++++++---- phpcs.xml.dist | 2 +- visitor.php | 21 +++++++++++++-------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/finder.php b/finder.php index 37808ed..7f45dd6 100644 --- a/finder.php +++ b/finder.php @@ -1,5 +1,7 @@ in('source/wordpress') // Shim for load-styles.php and load-scripts.php. @@ -41,5 +43,4 @@ //->notPath('wp-includes/theme-compat/footer.php') //->notPath('wp-includes/theme-compat/header.php') //->notPath('wp-includes/theme-compat/sidebar.php') - ->sortByName() -; + ->sortByName(); diff --git a/functionMap.php b/functionMap.php index 1642088..01ba11d 100644 --- a/functionMap.php +++ b/functionMap.php @@ -1,8 +1,10 @@ , filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error'; -if (file_exists(__DIR__ . '/source/wordpress/wp-includes/Requests/Cookie/Jar.php')) { +if (file_exists(sprintf('%s/source/wordpress/wp-includes/Requests/Cookie/Jar.php', __DIR__))) { $httpReturnType = 'array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error'; } $cronArgsType = 'list'; @@ -82,11 +84,11 @@ 'get_attachment_taxonomies' => ["(\$output is 'names' ? array : array)"], 'get_taxonomies_for_attachments' => ["(\$output is 'names' ? array : array)"], 'get_post_stati' => ["(\$output is 'names' ? array : array)"], - 'get_comment' => ["(\$comment is \WP_Comment ? array|\WP_Comment : array|\WP_Comment|null) & (\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Comment|null))", 'output'=>"'OBJECT'|'ARRAY_A'|'ARRAY_N'"], - 'get_post' => ["(\$post is \WP_Post ? array|\WP_Post : array|\WP_Post|null) & (\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Post|null))", 'output'=>"'OBJECT'|'ARRAY_A'|'ARRAY_N'" ], + 'get_comment' => ["(\$comment is \WP_Comment ? array|\WP_Comment : array|\WP_Comment|null) & (\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Comment|null))", 'output' => "'OBJECT'|'ARRAY_A'|'ARRAY_N'"], + 'get_post' => ["(\$post is \WP_Post ? array|\WP_Post : array|\WP_Post|null) & (\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Post|null))", 'output' => "'OBJECT'|'ARRAY_A'|'ARRAY_N'" ], 'get_term_by' => ["(\$output is 'ARRAY_A' ? array|\WP_Error|false : (\$output is 'ARRAY_N' ? list|\WP_Error|false : \WP_Term|\WP_Error|false))"], 'get_page_by_path' => ["(\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Post|null))"], - 'get_term' => ["(\$output is 'ARRAY_A' ? array|\WP_Error|null : (\$output is 'ARRAY_N' ? list|\WP_Error|null : \WP_Term|\WP_Error|null))", 'output'=>"'OBJECT'|'ARRAY_A'|'ARRAY_N'"], + 'get_term' => ["(\$output is 'ARRAY_A' ? array|\WP_Error|null : (\$output is 'ARRAY_N' ? list|\WP_Error|null : \WP_Term|\WP_Error|null))", 'output' => "'OBJECT'|'ARRAY_A'|'ARRAY_N'"], 'has_action' => ['($callback is false ? bool : false|int)'], 'has_filter' => ['($callback is false ? bool : false|int)'], 'get_permalink' => ['($post is \WP_Post ? string : string|false)'], diff --git a/phpcs.xml.dist b/phpcs.xml.dist index fb3af31..4f79a2d 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -2,7 +2,7 @@ finder.php functionMap.php - visitor.php + diff --git a/visitor.php b/visitor.php index 648ee05..1295729 100644 --- a/visitor.php +++ b/visitor.php @@ -89,7 +89,7 @@ public function format(): array '%s %s%s', $this->tag, $this->type, - ($this->name !== null) ? (' $' . $this->name) : '' + $this->name !== null ? " \$$this->name" : '' ), ]; } @@ -114,7 +114,7 @@ public function format(): array return []; } - $name = ($this->name !== null) ? (' $' . $this->name) : ''; + $name = $this->name !== null ? " \$$this->name" : ''; if ($this->isArrayShape()) { $strings[] = sprintf( @@ -144,7 +144,7 @@ public function format(): array $description = ''; if ($this->description !== null) { - $description = ' ' . $this->description; + $description = " $this->description"; } $strings[] = $this->isArrayShape() @@ -211,10 +211,10 @@ public function format(int $level = 1): array if ($this->isArrayShape()) { if ($this->name !== null) { - $strings[] = $padding . '},'; + $strings[] = "$padding},"; } } else { - $strings[] = $padding . '}>,'; + $strings[] = "$padding}>,"; } } else { $strings[] = sprintf( @@ -625,7 +625,7 @@ static function (WordPressTag $tag) use ($matchNames): bool { } /** - * @return string[] + * @return list */ private function getAdditionalTagsFromMap(string $symbolName): array { @@ -867,10 +867,15 @@ private static function getTypeNameFromString(string $tagVariable): ?string return null; } - $tagVariableType = str_replace([ + $tagVariableType = str_replace( + [ 'stdClass', '\\object', - ], 'object', $tagVariableType); + ], + 'object', + $tagVariableType + ); + $supportedTypes = [ 'object', 'array', From 0a7169012963358b547732d11c496155953a8d9b Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:11:47 +0100 Subject: [PATCH 6/8] Add missing `use` statement --- tests/data/get_posts.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/data/get_posts.php b/tests/data/get_posts.php index e6c8d6a..ebc7661 100644 --- a/tests/data/get_posts.php +++ b/tests/data/get_posts.php @@ -4,6 +4,8 @@ namespace PhpStubs\WordPress\Core\Tests; +use function PHPStan\Testing\assertType; + assertType('array', get_posts()); assertType('array', get_posts(['key' => 'value'])); assertType('array', get_posts(['fields' => ''])); From 8d4a4f604f45ffc789272a113566792ad472647a Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Fri, 10 Nov 2023 00:01:43 +0100 Subject: [PATCH 7/8] Fix asserttype for empty string See https://github.com/php-stubs/wordpress-stubs/pull/134#issuecomment-1804812045 --- tests/data/get_posts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/get_posts.php b/tests/data/get_posts.php index ebc7661..31d019f 100644 --- a/tests/data/get_posts.php +++ b/tests/data/get_posts.php @@ -57,7 +57,7 @@ assertType('array', get_posts(['fields' => $union])); $union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; -assertType('array', get_posts([$union => ''])); +assertType('array', get_posts([$union => ''])); $union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; assertType('array', get_posts([$union => 'ids'])); From 5b19752908d2607d733e1a282573b486706f99f9 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Fri, 10 Nov 2023 02:07:08 +0100 Subject: [PATCH 8/8] Simplify condition --- functionMap.php | 2 +- wordpress-stubs.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functionMap.php b/functionMap.php index 01ba11d..1b97d4c 100644 --- a/functionMap.php +++ b/functionMap.php @@ -24,7 +24,7 @@ * @link https://github.com/phpstan/phpstan-src/blob/1.10.x/resources/functionMap.php */ return [ - 'get_posts' => ["(\$args is array{'fields': 'id=>parent'}|array{'fields': 'ids'} ? array : array)"], + 'get_posts' => ["(\$args is array{'fields': 'id=>parent'|'ids'} ? array : array)"], 'addslashes_gpc' => ['T', '@phpstan-template' => 'T', 'gpc' => 'T'], 'have_posts' => [null, '@phpstan-impure' => ''], 'rawurlencode_deep' => ['T', '@phpstan-template' => 'T', 'value' => 'T'], diff --git a/wordpress-stubs.php b/wordpress-stubs.php index 4bd8e00..e318c6f 100644 --- a/wordpress-stubs.php +++ b/wordpress-stubs.php @@ -126599,7 +126599,7 @@ function is_post_publicly_viewable($post = \null) * w?: int, * year?: int, * } $args - * @phpstan-return ($args is array{'fields': 'id=>parent'}|array{'fields': 'ids'} ? array : array) + * @phpstan-return ($args is array{'fields': 'id=>parent'|'ids'} ? array : array) */ function get_posts($args = \null) {