From 09f558309a08e22d1c2fde5ec85afb666159f0d2 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sun, 16 Apr 2023 10:31:44 +0200 Subject: [PATCH 01/30] Remove has_filter extension --- extension.neon | 4 -- ...lterDynamicFunctionReturnTypeExtension.php | 48 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/has_filter.php | 30 ------------ 4 files changed, 83 deletions(-) delete mode 100644 src/HasFilterDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/has_filter.php diff --git a/extension.neon b/extension.neon index 40b18148..d92ba772 100644 --- a/extension.neon +++ b/extension.neon @@ -59,10 +59,6 @@ services: class: SzepeViktor\PHPStan\WordPress\GetCommentDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\HasFilterDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\ShortcodeAttsDynamicFunctionReturnTypeExtension tags: diff --git a/src/HasFilterDynamicFunctionReturnTypeExtension.php b/src/HasFilterDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index f928b422..00000000 --- a/src/HasFilterDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,48 +0,0 @@ -getName(), ['has_filter', 'has_action'], true); - } - - // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $args = $functionCall->getArgs(); - $callbackArgumentType = new ConstantBooleanType(false); - - if (isset($args[1])) { - $callbackArgumentType = $scope->getType($args[1]->value); - } - - if ($callbackArgumentType->isFalse()->yes()) { - return new BooleanType(); - } - - $returnType = [new ConstantBooleanType(false), new IntegerType()]; - if ($callbackArgumentType->isFalse()->maybe()) { - $returnType[] = new BooleanType(); - } - - return TypeCombinator::union(...$returnType); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 92ea1a16..c783083a 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -25,7 +25,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/get_posts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_sites.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_terms.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/has_filter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/mysql2date.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/shortcode_atts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/term_exists.php'); diff --git a/tests/data/has_filter.php b/tests/data/has_filter.php deleted file mode 100644 index b0b7f041..00000000 --- a/tests/data/has_filter.php +++ /dev/null @@ -1,30 +0,0 @@ - Date: Sun, 16 Apr 2023 10:39:59 +0200 Subject: [PATCH 02/30] Remove current_time extension --- extension.neon | 4 -- ...TimeDynamicFunctionReturnTypeExtension.php | 55 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/current_time.php | 28 ---------- 4 files changed, 88 deletions(-) delete mode 100644 src/CurrentTimeDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/current_time.php diff --git a/extension.neon b/extension.neon index d92ba772..c4db929c 100644 --- a/extension.neon +++ b/extension.neon @@ -67,10 +67,6 @@ services: class: SzepeViktor\PHPStan\WordPress\MySQL2DateDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\CurrentTimeDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\ApplyFiltersDynamicFunctionReturnTypeExtension tags: diff --git a/src/CurrentTimeDynamicFunctionReturnTypeExtension.php b/src/CurrentTimeDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index 97bd72a4..00000000 --- a/src/CurrentTimeDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,55 +0,0 @@ -getName() === 'current_time'; - } - - /** - * @see https://developer.wordpress.org/reference/functions/current_time/ - */ - // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - $argumentType = $scope->getType($functionCall->getArgs()[0]->value); - - // When called with a $type that isn't a constant string, return default return type - if (count($argumentType->getConstantStrings()) === 0) { - return null; - } - - // Called with a constant string $type - $returnType = []; - foreach ($argumentType->getConstantStrings() as $constantString) { - switch ($constantString->getValue()) { - case 'timestamp': - case 'U': - $returnType[] = new IntegerType(); - break; - case 'mysql': - default: - $returnType[] = new StringType(); - } - } - - return TypeCombinator::union(...$returnType); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index c783083a..c8dbb8b9 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -14,7 +14,6 @@ public function dataFileAsserts(): iterable // Path to a file with actual asserts of expected types: yield from $this->gatherAssertTypes(__DIR__ . '/data/_get_list_table.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/apply_filters.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/current_time.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_key.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/esc_sql.php'); diff --git a/tests/data/current_time.php b/tests/data/current_time.php deleted file mode 100644 index 4a229f66..00000000 --- a/tests/data/current_time.php +++ /dev/null @@ -1,28 +0,0 @@ - Date: Sun, 16 Apr 2023 12:17:50 +0200 Subject: [PATCH 03/30] Remove mysql2date extension --- extension.neon | 4 -- ...DateDynamicFunctionReturnTypeExtension.php | 56 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/mysql2date.php | 25 --------- 4 files changed, 86 deletions(-) delete mode 100644 src/MySQL2DateDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/mysql2date.php diff --git a/extension.neon b/extension.neon index c4db929c..33092e0b 100644 --- a/extension.neon +++ b/extension.neon @@ -63,10 +63,6 @@ services: class: SzepeViktor\PHPStan\WordPress\ShortcodeAttsDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\MySQL2DateDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\ApplyFiltersDynamicFunctionReturnTypeExtension tags: diff --git a/src/MySQL2DateDynamicFunctionReturnTypeExtension.php b/src/MySQL2DateDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index 3a553fa3..00000000 --- a/src/MySQL2DateDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,56 +0,0 @@ -getName() === 'mysql2date'; - } - - /** - * @see https://developer.wordpress.org/reference/functions/mysql2date/ - */ - // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - $argumentType = $scope->getType($functionCall->getArgs()[0]->value); - - // When called with a $format that isn't a constant string, return default return type - if (count($argumentType->getConstantStrings()) === 0) { - return null; - } - - // Called with a constant string $format - $returnType = []; - foreach ($argumentType->getConstantStrings() as $constantString) { - switch ($constantString->getValue()) { - case 'G': - case 'U': - $returnType[] = new UnionType([new ConstantBooleanType(false), new IntegerType()]); - break; - default: - $returnType[] = new UnionType([new ConstantBooleanType(false), new StringType()]); - } - } - - return TypeCombinator::union(...$returnType); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index c8dbb8b9..917c6377 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -24,7 +24,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/get_posts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_sites.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_terms.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/mysql2date.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/shortcode_atts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/term_exists.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_error_parameter.php'); diff --git a/tests/data/mysql2date.php b/tests/data/mysql2date.php deleted file mode 100644 index 7eddccc1..00000000 --- a/tests/data/mysql2date.php +++ /dev/null @@ -1,25 +0,0 @@ - Date: Sun, 16 Apr 2023 12:20:27 +0200 Subject: [PATCH 04/30] Remove get_object_taxonomies extension --- extension.neon | 4 -- ...miesDynamicFunctionReturnTypeExtension.php | 65 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/get_object_taxonomies.php | 18 ----- 4 files changed, 88 deletions(-) delete mode 100644 src/GetObjectTaxonomiesDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/get_object_taxonomies.php diff --git a/extension.neon b/extension.neon index 33092e0b..0a3ea185 100644 --- a/extension.neon +++ b/extension.neon @@ -51,10 +51,6 @@ services: class: SzepeViktor\PHPStan\WordPress\GetTaxonomiesDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\GetObjectTaxonomiesDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\GetCommentDynamicFunctionReturnTypeExtension tags: diff --git a/src/GetObjectTaxonomiesDynamicFunctionReturnTypeExtension.php b/src/GetObjectTaxonomiesDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index 798419dd..00000000 --- a/src/GetObjectTaxonomiesDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,65 +0,0 @@ -getName(), ['get_object_taxonomies'], true); - } - - /** - * @see https://developer.wordpress.org/reference/functions/get_object_taxonomies/ - * - * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter - */ - // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - $args = $functionCall->getArgs(); - - // Called without second $output argument - if (count($args) <= 1) { - return new ArrayType(new IntegerType(), new StringType()); - } - - $argumentType = $scope->getType($args[1]->value); - - // When called with an $output that isn't a constant string, return default return type - if (count($argumentType->getConstantStrings()) === 0) { - return null; - } - - // Called with a constant string $output - $returnType = []; - foreach ($argumentType->getConstantStrings() as $constantString) { - switch ($constantString->getValue()) { - case 'objects': - $returnType[] = new ArrayType(new StringType(), new ObjectType('WP_Taxonomy')); - break; - case 'names': - default: - $returnType[] = new ArrayType(new IntegerType(), new StringType()); - } - } - - return TypeCombinator::union(...$returnType); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 917c6377..125e6b83 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -18,7 +18,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/esc_sql.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_comment.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/get_object_taxonomies.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_permalink.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_posts.php'); diff --git a/tests/data/get_object_taxonomies.php b/tests/data/get_object_taxonomies.php deleted file mode 100644 index c0ef54b5..00000000 --- a/tests/data/get_object_taxonomies.php +++ /dev/null @@ -1,18 +0,0 @@ -', get_object_taxonomies('post')); -assertType('array', get_object_taxonomies('post', 'names')); -assertType('array', get_object_taxonomies('post', 'Hello')); - -// Unknown output -assertType('array', get_object_taxonomies('post', $_GET['foo'])); - -// Objects output -assertType('array', get_object_taxonomies('post', 'objects')); From 40d947d7270079f44bc556a63af60f8e602cbd26 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:22:29 +0200 Subject: [PATCH 05/30] Remove get_taxonomies extension --- extension.neon | 4 -- ...miesDynamicFunctionReturnTypeExtension.php | 69 ------------------- 2 files changed, 73 deletions(-) delete mode 100644 src/GetTaxonomiesDynamicFunctionReturnTypeExtension.php diff --git a/extension.neon b/extension.neon index 0a3ea185..fea598f1 100644 --- a/extension.neon +++ b/extension.neon @@ -47,10 +47,6 @@ services: class: SzepeViktor\PHPStan\WordPress\GetSitesDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\GetTaxonomiesDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\GetCommentDynamicFunctionReturnTypeExtension tags: diff --git a/src/GetTaxonomiesDynamicFunctionReturnTypeExtension.php b/src/GetTaxonomiesDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index ef1e66e5..00000000 --- a/src/GetTaxonomiesDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,69 +0,0 @@ -getName() === 'get_taxonomies'; - } - - /** - * @see https://developer.wordpress.org/reference/functions/get_taxonomies/ - * - * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter - */ - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $objectsReturnType = new ArrayType(new StringType(), new ObjectType('WP_Taxonomy')); - $namesReturnType = new ArrayType(new StringType(), new StringType()); - - $args = $functionCall->getArgs(); - - // Called without second $output arguments - if (count($args) <= 1) { - return $namesReturnType; - } - - $argumentType = $scope->getType($args[1]->value); - - // When called with a non-string $output, return default return type - if (count($argumentType->getConstantStrings()) === 0) { - return TypeCombinator::union( - $objectsReturnType, - $namesReturnType - ); - } - - // Called with a string $output - $returnType = []; - foreach ($argumentType->getConstantStrings() as $constantString) { - switch ($constantString->getValue()) { - case 'objects': - $returnType[] = $objectsReturnType; - break; - case 'names': - default: - $returnType[] = $namesReturnType; - } - } - - return TypeCombinator::union(...$returnType); - } -} From 3dfaebc29913b51cec2c546959157de7de059601 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:24:43 +0200 Subject: [PATCH 06/30] Adapt get_post extension to new stub file --- ...PostDynamicFunctionReturnTypeExtension.php | 50 ++++--------------- tests/data/get_post.php | 22 +------- 2 files changed, 11 insertions(+), 61 deletions(-) diff --git a/src/GetPostDynamicFunctionReturnTypeExtension.php b/src/GetPostDynamicFunctionReturnTypeExtension.php index 0c54501b..5f01c1cb 100644 --- a/src/GetPostDynamicFunctionReturnTypeExtension.php +++ b/src/GetPostDynamicFunctionReturnTypeExtension.php @@ -11,12 +11,8 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\ArrayType; -use PHPStan\Type\IntegerType; -use PHPStan\Type\MixedType; -use PHPStan\Type\NullType; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\ObjectType; -use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use WP_Post; @@ -25,56 +21,30 @@ class GetPostDynamicFunctionReturnTypeExtension implements \PHPStan\Type\Dynamic { public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return in_array($functionReflection->getName(), ['get_post', 'get_page_by_path'], true); + return $functionReflection->getName() === 'get_post'; } /** * @see https://developer.wordpress.org/reference/functions/get_post/ - * @see https://developer.wordpress.org/reference/functions/get_page_by_path/ */ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { $args = $functionCall->getArgs(); - $returnType = [new NullType()]; // When called with an instance of WP_Post if ( - $functionReflection->getName() === 'get_post' && count($args) > 0 && (new ObjectType(WP_Post::class))->isSuperTypeOf($scope->getType($args[0]->value))->yes() ) { - $returnType = []; + return TypeCombinator::removeNull( + ParametersAcceptorSelector::selectFromArgs( + $scope, + $args, + $functionReflection->getVariants() + )->getReturnType() + ); } - if (count($args) < 2) { - $returnType[] = new ObjectType(WP_Post::class); - } - - if (count($args) >= 2) { - $outputType = $scope->getType($args[1]->value); - - // When called with an $output that isn't a constant string - if (count($outputType->getConstantStrings()) === 0) { - $returnType[] = new ArrayType(new StringType(), new MixedType()); - $returnType[] = new ArrayType(new IntegerType(), new MixedType()); - $returnType[] = new ObjectType(WP_Post::class); - return TypeCombinator::union(...$returnType); - } - - foreach ($outputType->getConstantStrings() as $constantString) { - switch ($constantString->getValue()) { - case 'ARRAY_A': - $returnType[] = new ArrayType(new StringType(), new MixedType()); - break; - case 'ARRAY_N': - $returnType[] = new ArrayType(new IntegerType(), new MixedType()); - break; - default: - $returnType[] = new ObjectType(WP_Post::class); - } - } - } - - return TypeCombinator::union(...$returnType); + return null; } } diff --git a/tests/data/get_post.php b/tests/data/get_post.php index 6e03324f..0ace5792 100644 --- a/tests/data/get_post.php +++ b/tests/data/get_post.php @@ -4,32 +4,12 @@ namespace SzepeViktor\PHPStan\WordPress\Tests; -use stdClass; use function PHPStan\Testing\assertType; /** @var \WP_Post $wpPostType */ -$wpPostType = new stdClass(); +$wpPostType = null; -// Default output -assertType('WP_Post|null', get_post()); -assertType('WP_Post|null', get_post(1)); -assertType('WP_Post|null', get_post(1, OBJECT)); -assertType('array|WP_Post|null', get_post(1, $_GET['foo'])); assertType('WP_Post', get_post($wpPostType)); assertType('WP_Post', get_post($wpPostType, OBJECT)); -assertType('array|WP_Post', get_post($wpPostType, $_GET['foo'])); -assertType('WP_Post|null', get_page_by_path('page/path')); -assertType('WP_Post|null', get_page_by_path('page/path', OBJECT)); -assertType('array|WP_Post|null', get_page_by_path('page/path', $_GET['foo'])); - -// Associative array output -assertType('array|null', get_post(null, ARRAY_A)); -assertType('array|null', get_post(1, ARRAY_A)); assertType('array', get_post($wpPostType, ARRAY_A)); -assertType('array|null', get_page_by_path('page/path', ARRAY_A)); - -// Numeric array output -assertType('array|null', get_post(null, ARRAY_N)); -assertType('array|null', get_post(1, ARRAY_N)); assertType('array', get_post($wpPostType, ARRAY_N)); -assertType('array|null', get_page_by_path('page/path', ARRAY_N)); From d4181ecba6a0ecbef7f94ec52ba5ff934616f78f Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:42:32 +0200 Subject: [PATCH 07/30] Adapt get_comment extension to new stub file --- ...mentDynamicFunctionReturnTypeExtension.php | 52 ++++--------------- tests/data/get_comment.php | 18 ++----- 2 files changed, 12 insertions(+), 58 deletions(-) diff --git a/src/GetCommentDynamicFunctionReturnTypeExtension.php b/src/GetCommentDynamicFunctionReturnTypeExtension.php index f310c7dd..35e87bfb 100644 --- a/src/GetCommentDynamicFunctionReturnTypeExtension.php +++ b/src/GetCommentDynamicFunctionReturnTypeExtension.php @@ -13,12 +13,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Type; -use PHPStan\Type\ArrayType; -use PHPStan\Type\StringType; -use PHPStan\Type\IntegerType; -use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; -use PHPStan\Type\NullType; use PHPStan\Type\TypeCombinator; use WP_Comment; @@ -32,50 +27,21 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { $args = $functionCall->getArgs(); - $returnType = [new NullType()]; + // When called with an instance of WP_Comment if ( count($args) > 0 && (new ObjectType(WP_Comment::class))->isSuperTypeOf($scope->getType($args[0]->value))->yes() ) { - $returnType = []; + return TypeCombinator::removeNull( + ParametersAcceptorSelector::selectFromArgs( + $scope, + $args, + $functionReflection->getVariants() + )->getReturnType() + ); } - if (count($args) < 2) { - $returnType[] = new ObjectType(WP_Comment::class); - } - - if (count($args) >= 2) { - $outputType = $scope->getType($args[1]->value); - - // When called with an $output that isn't a constant string, return default return type - if (count($outputType->getConstantStrings()) === 0) { - if ($returnType === []) { - return TypeCombinator::removeNull( - ParametersAcceptorSelector::selectFromArgs( - $scope, - $args, - $functionReflection->getVariants() - )->getReturnType() - ); - } - return null; - } - - foreach ($outputType->getConstantStrings() as $constantString) { - switch ($constantString->getValue()) { - case 'ARRAY_A': - $returnType[] = new ArrayType(new StringType(), new MixedType()); - break; - case 'ARRAY_N': - $returnType[] = new ArrayType(new IntegerType(), new MixedType()); - break; - default: - $returnType[] = new ObjectType(WP_Comment::class); - } - } - } - - return TypeCombinator::union(...$returnType); + return null; } } diff --git a/tests/data/get_comment.php b/tests/data/get_comment.php index 2e2ffcee..910d1b8f 100644 --- a/tests/data/get_comment.php +++ b/tests/data/get_comment.php @@ -9,20 +9,8 @@ /** @var \WP_Comment $comment */ $comment = $comment; -// Default output -assertType('WP_Comment|null', get_comment()); -assertType('WP_Comment|null', get_comment(1)); -assertType('WP_Comment|null', get_comment(1, OBJECT)); -assertType('WP_Comment|null', get_comment(1, 'Hello')); - -// Unknown output -assertType('array|WP_Comment|null', get_comment(1, $_GET['foo'])); -assertType('array|WP_Comment', get_comment($comment, $_GET['foo'])); - -// Associative array output -assertType('array|null', get_comment(1, ARRAY_A)); +assertType('WP_Comment', get_comment($comment)); +assertType('WP_Comment', get_comment($comment, OBJECT)); assertType('array', get_comment($comment, ARRAY_A)); - -// Numeric array output -assertType('array|null', get_comment(1, ARRAY_N)); assertType('array', get_comment($comment, ARRAY_N)); +assertType('array|WP_Comment', get_comment($comment, $_GET['foo'])); From e1f1ac3d89705b87d7a217e15cdf07895c0b9b17 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sun, 16 Apr 2023 13:36:58 +0200 Subject: [PATCH 08/30] Fix CS --- src/GetCommentDynamicFunctionReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GetCommentDynamicFunctionReturnTypeExtension.php b/src/GetCommentDynamicFunctionReturnTypeExtension.php index 35e87bfb..e2f49744 100644 --- a/src/GetCommentDynamicFunctionReturnTypeExtension.php +++ b/src/GetCommentDynamicFunctionReturnTypeExtension.php @@ -42,6 +42,6 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ); } - return null; + return null; } } From d15b7e138fe3c1059458c397f563a69ec5fb9282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sun, 16 Apr 2023 21:28:09 +0200 Subject: [PATCH 09/30] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9d7e0fde..e903adfd 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", + "php-stubs/wordpress-stubs": "dev-master", "phpstan/phpstan": "^1.10.0", "symfony/polyfill-php73": "^1.12.0" }, From 02b8391b80d0f2b70c1db68caf988954a7bad3d3 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:18:04 +0200 Subject: [PATCH 10/30] Remove WP_Theme::get() --- extension.neon | 4 - ...emeGetDynamicMethodReturnTypeExtension.php | 85 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/wp_theme_get.php | 24 ------ 4 files changed, 114 deletions(-) delete mode 100644 src/WpThemeGetDynamicMethodReturnTypeExtension.php delete mode 100644 tests/data/wp_theme_get.php diff --git a/extension.neon b/extension.neon index fea598f1..a0cdf234 100644 --- a/extension.neon +++ b/extension.neon @@ -1,8 +1,4 @@ services: - - - class: SzepeViktor\PHPStan\WordPress\WpThemeGetDynamicMethodReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\IsWpErrorFunctionTypeSpecifyingExtension tags: diff --git a/src/WpThemeGetDynamicMethodReturnTypeExtension.php b/src/WpThemeGetDynamicMethodReturnTypeExtension.php deleted file mode 100644 index bbf7b0ac..00000000 --- a/src/WpThemeGetDynamicMethodReturnTypeExtension.php +++ /dev/null @@ -1,85 +0,0 @@ - - */ - protected static $headers = [ - 'Name', - 'ThemeURI', - 'Description', - 'Author', - 'AuthorURI', - 'Version', - 'Template', - 'Status', - 'Tags', - 'TextDomain', - 'DomainPath', - 'RequiresWP', - 'RequiresPHP', - ]; - - public function getClass(): string - { - return WP_Theme::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'get'; - } - - /** - * @see https://developer.wordpress.org/reference/classes/wp_theme/get/ - * - * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter - */ - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $argumentType = $scope->getType($methodCall->getArgs()[0]->value); - - if (count($argumentType->getConstantStrings()) === 0) { - return TypeCombinator::union( - new StringType(), - new ArrayType(new IntegerType(), new StringType()), - new ConstantBooleanType(false) - ); - } - - $returnType = []; - foreach ($argumentType->getConstantStrings() as $constantString) { - if ($constantString->getValue() === 'Tags') { - $returnType[] = new ArrayType(new IntegerType(), new StringType()); - } elseif (in_array($constantString->getValue(), self::$headers, true)) { - $returnType[] = new StringType(); - } else { - $returnType[] = new ConstantBooleanType(false); - } - } - - return TypeCombinator::union(...$returnType); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 125e6b83..1addcdea 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -27,7 +27,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/term_exists.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_error_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_parse_url.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_theme_get.php'); } /** diff --git a/tests/data/wp_theme_get.php b/tests/data/wp_theme_get.php deleted file mode 100644 index 2de5fa26..00000000 --- a/tests/data/wp_theme_get.php +++ /dev/null @@ -1,24 +0,0 @@ -get('Name')); -assertType('string', $theme->get('ThemeURI')); -assertType('string', $theme->get('Description')); -assertType('string', $theme->get('Author')); -assertType('string', $theme->get('AuthorURI')); -assertType('string', $theme->get('Version')); -assertType('string', $theme->get('Template')); -assertType('string', $theme->get('Status')); -assertType('array', $theme->get('Tags')); -assertType('string', $theme->get('TextDomain')); -assertType('string', $theme->get('DomainPath')); -assertType('string', $theme->get('RequiresWP')); -assertType('false', $theme->get('foo')); -assertType('array|string|false', $theme->get($_GET['foo'])); From 0971d6786854bb33ac1d6e195de821fcc030cd03 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Wed, 19 Apr 2023 23:17:00 +0200 Subject: [PATCH 11/30] Remove get_permalink extension --- extension.neon | 4 -- ...linkDynamicFunctionReturnTypeExtension.php | 57 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/get_permalink.php | 30 ---------- 4 files changed, 92 deletions(-) delete mode 100644 src/GetPermalinkDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/get_permalink.php diff --git a/extension.neon b/extension.neon index a0cdf234..0ec57b45 100644 --- a/extension.neon +++ b/extension.neon @@ -11,10 +11,6 @@ services: class: SzepeViktor\PHPStan\WordPress\EchoKeyDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\GetPermalinkDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\GetListTableDynamicFunctionReturnTypeExtension tags: diff --git a/src/GetPermalinkDynamicFunctionReturnTypeExtension.php b/src/GetPermalinkDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index f993e20f..00000000 --- a/src/GetPermalinkDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,57 +0,0 @@ -getName(), - [ - 'get_permalink', - 'get_the_permalink', - 'get_post_permalink', - ], - true - ); - } - - /** - * @see https://developer.wordpress.org/reference/functions/get_permalink/ - * @see https://developer.wordpress.org/reference/functions/get_the_permalink/ - * - * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter - */ - // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - $args = $functionCall->getArgs(); - - if (count($args) !== 0) { - $type = $scope->getType($args[0]->value); - - // Called with a WP_Post instance - if ((new ObjectType(WP_Post::class))->isSuperTypeOf($type)->yes()) { - return new StringType(); - } - } - - // When called without arguments or with a $type that isn't a WP_Post instance, return default return type - return null; - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 1addcdea..2b40b8ca 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -18,7 +18,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/esc_sql.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_comment.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/get_permalink.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_posts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_sites.php'); diff --git a/tests/data/get_permalink.php b/tests/data/get_permalink.php deleted file mode 100644 index 30558056..00000000 --- a/tests/data/get_permalink.php +++ /dev/null @@ -1,30 +0,0 @@ - Date: Wed, 19 Apr 2023 23:40:43 +0200 Subject: [PATCH 12/30] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2175101e..c62b0c70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ # TravisCI configuration for szepeviktor/phpstan-wordpress -if: "branch = master" +if: "branch IN (master, 2.x)" language: "php" os: From 7a9a1732caae2be0a8ad3fefadc454fc31a8f33d Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:38:42 +0200 Subject: [PATCH 13/30] Remove term_exists extension --- extension.neon | 4 - ...istsDynamicFunctionReturnTypeExtension.php | 93 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/term_exists.php | 39 -------- 4 files changed, 137 deletions(-) delete mode 100644 src/TermExistsDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/term_exists.php diff --git a/extension.neon b/extension.neon index 0ec57b45..c8a93a8c 100644 --- a/extension.neon +++ b/extension.neon @@ -59,10 +59,6 @@ services: class: SzepeViktor\PHPStan\WordPress\WPErrorParameterDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\TermExistsDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\WpParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/TermExistsDynamicFunctionReturnTypeExtension.php b/src/TermExistsDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index 2d5e83e2..00000000 --- a/src/TermExistsDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,93 +0,0 @@ -getName(), - [ - 'is_term', - 'tag_exists', - 'term_exists', - ], - true - ); - } - - /** - * @see https://developer.wordpress.org/reference/functions/term_exists/ - * - * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter - */ - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $args = $functionCall->getArgs(); - - if (count($args) === 0) { - return new MixedType(); - } - - $termType = $scope->getType($args[0]->value); - if ($termType->isNull()->yes()) { - return $termType; - } - - $returnType = [new NullType()]; - - if (($termType instanceof ConstantIntegerType)) { - if ($termType->getValue() === 0) { - return new ConstantIntegerType(0); - } - } elseif ($termType->isInteger()->no() === false) { - $returnType[] = new ConstantIntegerType(0); - } - - $withTaxonomy = new ConstantArrayType( - [ - new ConstantStringType('term_id'), - new ConstantStringType('term_taxonomy_id'), - ], - [ - new StringType(), - new StringType(), - ] - ); - $withoutTaxonomy = new StringType(); - - $taxonomyType = isset($args[1]) ? $scope->getType($args[1]->value) : new ConstantStringType(''); - - if (count($taxonomyType->getConstantStrings()) === 0) { - $returnType[] = $withTaxonomy; - $returnType[] = $withoutTaxonomy; - return TypeCombinator::union(...$returnType); - } - - foreach ($taxonomyType->getConstantStrings() as $constantString) { - $returnType[] = $constantString->getValue() === '' - ? $withoutTaxonomy - : $withTaxonomy; - } - return TypeCombinator::union(...$returnType); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 2b40b8ca..5d0e9160 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -23,7 +23,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/get_sites.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_terms.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/shortcode_atts.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/term_exists.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_error_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_parse_url.php'); } diff --git a/tests/data/term_exists.php b/tests/data/term_exists.php deleted file mode 100644 index 7ec23538..00000000 --- a/tests/data/term_exists.php +++ /dev/null @@ -1,39 +0,0 @@ - Date: Thu, 20 Apr 2023 21:55:21 +0200 Subject: [PATCH 14/30] Update wp_error parameter extension --- ...eterDynamicFunctionReturnTypeExtension.php | 78 ----- tests/data/wp_error_parameter.php | 287 +----------------- 2 files changed, 1 insertion(+), 364 deletions(-) diff --git a/src/WPErrorParameterDynamicFunctionReturnTypeExtension.php b/src/WPErrorParameterDynamicFunctionReturnTypeExtension.php index c3c0fa70..7264ea28 100644 --- a/src/WPErrorParameterDynamicFunctionReturnTypeExtension.php +++ b/src/WPErrorParameterDynamicFunctionReturnTypeExtension.php @@ -18,90 +18,12 @@ class WPErrorParameterDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { private const SUPPORTED_FUNCTIONS = [ - 'wp_insert_link' => [ - 'arg' => 1, - 'false' => '0|positive-int', - 'true' => 'positive-int|WP_Error', - 'maybe' => '0|positive-int|WP_Error', - ], - 'wp_insert_category' => [ - 'arg' => 1, - 'false' => '0|positive-int', - 'true' => 'positive-int|WP_Error', - 'maybe' => '0|positive-int|WP_Error', - ], - 'wp_set_comment_status' => [ - 'arg' => 2, - 'false' => 'bool', - 'true' => 'true|WP_Error', - 'maybe' => 'bool|WP_Error', - ], - 'wp_update_comment' => [ - 'arg' => 1, - 'false' => '0|1|false', - 'true' => '0|1|WP_Error', - 'maybe' => '0|1|false|WP_Error', - ], - 'wp_schedule_single_event' => [ - 'arg' => 3, - 'false' => 'bool', - 'true' => 'true|WP_Error', - 'maybe' => 'bool|WP_Error', - ], - 'wp_schedule_event' => [ - 'arg' => 4, - 'false' => 'bool', - 'true' => 'true|WP_Error', - 'maybe' => 'bool|WP_Error', - ], - 'wp_reschedule_event' => [ - 'arg' => 4, - 'false' => 'bool', - 'true' => 'true|WP_Error', - 'maybe' => 'bool|WP_Error', - ], - 'wp_unschedule_event' => [ - 'arg' => 3, - 'false' => 'bool', - 'true' => 'true|WP_Error', - 'maybe' => 'bool|WP_Error', - ], - 'wp_clear_scheduled_hook' => [ - 'arg' => 2, - 'false' => '0|positive-int|false', - 'true' => '0|positive-int|WP_Error', - 'maybe' => '0|positive-int|false|WP_Error', - ], - 'wp_unschedule_hook' => [ - 'arg' => 1, - 'false' => '0|positive-int|false', - 'true' => '0|positive-int|WP_Error', - 'maybe' => '0|positive-int|false|WP_Error', - ], '_set_cron_array' => [ 'arg' => 1, 'false' => 'bool', 'true' => 'true|WP_Error', 'maybe' => 'bool|WP_Error', ], - 'wp_insert_post' => [ - 'arg' => 1, - 'false' => '0|positive-int', - 'true' => 'positive-int|WP_Error', - 'maybe' => '0|positive-int|WP_Error', - ], - 'wp_update_post' => [ - 'arg' => 1, - 'false' => '0|positive-int', - 'true' => 'positive-int|WP_Error', - 'maybe' => '0|positive-int|WP_Error', - ], - 'wp_insert_attachment' => [ - 'arg' => 3, - 'false' => '0|positive-int', - 'true' => 'positive-int|WP_Error', - 'maybe' => '0|positive-int|WP_Error', - ], ]; /** @var \PHPStan\PhpDoc\TypeStringResolver */ diff --git a/tests/data/wp_error_parameter.php b/tests/data/wp_error_parameter.php index f8d29eaa..63406725 100644 --- a/tests/data/wp_error_parameter.php +++ b/tests/data/wp_error_parameter.php @@ -9,184 +9,7 @@ namespace SzepeViktor\PHPStan\WordPress\Tests; use function PHPStan\Testing\assertType; -use function wp_clear_scheduled_hook; -use function wp_insert_attachment; -use function wp_insert_category; -use function wp_insert_link; -use function wp_insert_post; -use function wp_reschedule_event; -use function wp_schedule_event; -use function wp_schedule_single_event; -use function wp_set_comment_status; -use function wp_unschedule_event; -use function wp_unschedule_hook; -use function wp_update_comment; -use function wp_update_post; - -/** - * wp_insert_link() - */ -$value = wp_insert_link([]); -assertType('int<0, max>', $value); - -$value = wp_insert_link([], false); -assertType('int<0, max>', $value); - -$value = wp_insert_link([], true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_link([], $_GET['wp_error']); -assertType('int<0, max>|WP_Error', $value); - -/** - * wp_insert_category() - */ -$value = wp_insert_category([]); -assertType('int<0, max>', $value); - -$value = wp_insert_category([], false); -assertType('int<0, max>', $value); - -$value = wp_insert_category([], true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_category([], $_GET['wp_error']); -assertType('int<0, max>|WP_Error', $value); - -/** - * wp_set_comment_status() - */ -$value = wp_set_comment_status(1, 'spam'); -assertType('bool', $value); - -$value = wp_set_comment_status(1, 'spam', false); -assertType('bool', $value); - -$value = wp_set_comment_status(1, 'spam', true); -assertType('WP_Error|true', $value); - -$value = wp_set_comment_status(1, 'spam', $_GET['wp_error']); -assertType('bool|WP_Error', $value); - -/** - * wp_update_comment() - */ -$value = wp_update_comment([]); -assertType('0|1|false', $value); - -$value = wp_update_comment([], false); -assertType('0|1|false', $value); - -$value = wp_update_comment([], true); -assertType('0|1|WP_Error', $value); - -$value = wp_update_comment([], $_GET['wp_error']); -assertType('0|1|WP_Error|false', $value); - -/** - * wp_schedule_single_event() - */ -$value = wp_schedule_single_event(1, 'hook'); -assertType('bool', $value); - -$value = wp_schedule_single_event(1, 'hook', []); -assertType('bool', $value); - -$value = wp_schedule_single_event(1, 'hook', [], false); -assertType('bool', $value); - -$value = wp_schedule_single_event(1, 'hook', [], true); -assertType('WP_Error|true', $value); - -$value = wp_schedule_single_event(1, 'hook', [], $_GET['wp_error']); -assertType('bool|WP_Error', $value); - -/** - * wp_schedule_event() - */ -$value = wp_schedule_event(1, 'daily', 'hook'); -assertType('bool', $value); - -$value = wp_schedule_event(1, 'daily', 'hook', []); -assertType('bool', $value); - -$value = wp_schedule_event(1, 'daily', 'hook', [], false); -assertType('bool', $value); - -$value = wp_schedule_event(1, 'daily', 'hook', [], true); -assertType('WP_Error|true', $value); - -$value = wp_schedule_event(1, 'daily', 'hook', [], $_GET['wp_error']); -assertType('bool|WP_Error', $value); - -/** - * wp_reschedule_event() - */ -$value = wp_reschedule_event(1, 'daily', 'hook'); -assertType('bool', $value); - -$value = wp_reschedule_event(1, 'daily', 'hook', []); -assertType('bool', $value); - -$value = wp_reschedule_event(1, 'daily', 'hook', [], false); -assertType('bool', $value); - -$value = wp_reschedule_event(1, 'daily', 'hook', [], true); -assertType('WP_Error|true', $value); - -$value = wp_reschedule_event(1, 'daily', 'hook', [], $_GET['wp_error']); -assertType('bool|WP_Error', $value); - -/** - * wp_unschedule_event() - */ -$value = wp_unschedule_event(1, 'hook'); -assertType('bool', $value); - -$value = wp_unschedule_event(1, 'hook', []); -assertType('bool', $value); - -$value = wp_unschedule_event(1, 'hook', [], false); -assertType('bool', $value); - -$value = wp_unschedule_event(1, 'hook', [], true); -assertType('WP_Error|true', $value); - -$value = wp_unschedule_event(1, 'hook', [], $_GET['wp_error']); -assertType('bool|WP_Error', $value); - -/** - * wp_clear_scheduled_hook() - */ -$value = wp_clear_scheduled_hook('hook'); -assertType('int<0, max>|false', $value); - -$value = wp_clear_scheduled_hook('hook', []); -assertType('int<0, max>|false', $value); - -$value = wp_clear_scheduled_hook('hook', [], false); -assertType('int<0, max>|false', $value); - -$value = wp_clear_scheduled_hook('hook', [], true); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_clear_scheduled_hook('hook', [], $_GET['wp_error']); -assertType('int<0, max>|WP_Error|false', $value); - -/** - * wp_unschedule_hook() - */ -$value = wp_unschedule_hook('hook'); -assertType('int<0, max>|false', $value); - -$value = wp_unschedule_hook('hook', false); -assertType('int<0, max>|false', $value); - -$value = wp_unschedule_hook('hook', true); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_unschedule_hook('hook', $_GET['wp_error']); -assertType('int<0, max>|WP_Error|false', $value); +use function _set_cron_array; /** * _set_cron_array() @@ -202,111 +25,3 @@ $value = _set_cron_array([], $_GET['wp_error']); assertType('bool|WP_Error', $value); - -/** - * wp_insert_post() - */ -$value = wp_insert_post([]); -assertType('int<0, max>', $value); - -$value = wp_insert_post([], false); -assertType('int<0, max>', $value); - -$value = wp_insert_post([], false, true); -assertType('int<0, max>', $value); - -$value = wp_insert_post([], false, false); -assertType('int<0, max>', $value); - -$value = wp_insert_post([], true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_post([], true, true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_post([], true, false); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_post([], $_GET['wp_error']); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_insert_post([], $_GET['wp_error'], true); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_insert_post([], $_GET['wp_error'], false); -assertType('int<0, max>|WP_Error', $value); - -/** - * wp_update_post() - */ -$value = wp_update_post([]); -assertType('int<0, max>', $value); - -$value = wp_update_post([], false); -assertType('int<0, max>', $value); - -$value = wp_update_post([], false, true); -assertType('int<0, max>', $value); - -$value = wp_update_post([], false, false); -assertType('int<0, max>', $value); - -$value = wp_update_post([], true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_update_post([], true, true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_update_post([], true, false); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_update_post([], $_GET['wp_error']); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_update_post([], $_GET['wp_error'], true); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_update_post([], $_GET['wp_error'], false); -assertType('int<0, max>|WP_Error', $value); - -/** - * wp_insert_attachment() - */ -$value = wp_insert_attachment([]); -assertType('int<0, max>', $value); - -$value = wp_insert_attachment([], true); -assertType('int<0, max>', $value); - -$value = wp_insert_attachment([], false); -assertType('int<0, max>', $value); - -$value = wp_insert_attachment([], true, 1); -assertType('int<0, max>', $value); - -$value = wp_insert_attachment([], true, 1, false); -assertType('int<0, max>', $value); - -$value = wp_insert_attachment([], true, 1, false, false); -assertType('int<0, max>', $value); - -$value = wp_insert_attachment([], true, 1, false, true); -assertType('int<0, max>', $value); - -$value = wp_insert_attachment([], false, 0, true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_attachment([], false, 0, true, true); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_attachment([], false, 0, true, false); -assertType('int<1, max>|WP_Error', $value); - -$value = wp_insert_attachment([], true, 1, $_GET['wp_error']); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_insert_attachment([], true, 1, $_GET['wp_error'], true); -assertType('int<0, max>|WP_Error', $value); - -$value = wp_insert_attachment([], true, 1, $_GET['wp_error'], false); -assertType('int<0, max>|WP_Error', $value); From 0e7e243877de0b780d298a48390ff37f89a78808 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 22:03:04 +0200 Subject: [PATCH 15/30] Update GetPostDynamicFunctionReturnTypeExtension.php --- src/GetPostDynamicFunctionReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GetPostDynamicFunctionReturnTypeExtension.php b/src/GetPostDynamicFunctionReturnTypeExtension.php index 5f01c1cb..f4ad3c8b 100644 --- a/src/GetPostDynamicFunctionReturnTypeExtension.php +++ b/src/GetPostDynamicFunctionReturnTypeExtension.php @@ -27,7 +27,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo /** * @see https://developer.wordpress.org/reference/functions/get_post/ */ - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { $args = $functionCall->getArgs(); From 44e8c5d7c3cf2007e64eff726f96a4e76164502e Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 22:07:10 +0200 Subject: [PATCH 16/30] Fully remove wp_error parameter extension --- extension.neon | 4 - ...eterDynamicFunctionReturnTypeExtension.php | 75 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/wp_error_parameter.php | 27 ------- 4 files changed, 107 deletions(-) delete mode 100644 src/WPErrorParameterDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/wp_error_parameter.php diff --git a/extension.neon b/extension.neon index c8a93a8c..c5d2c737 100644 --- a/extension.neon +++ b/extension.neon @@ -55,10 +55,6 @@ services: class: SzepeViktor\PHPStan\WordPress\EchoParameterDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\WPErrorParameterDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\WpParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/WPErrorParameterDynamicFunctionReturnTypeExtension.php b/src/WPErrorParameterDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index 7264ea28..00000000 --- a/src/WPErrorParameterDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,75 +0,0 @@ - [ - 'arg' => 1, - 'false' => 'bool', - 'true' => 'true|WP_Error', - 'maybe' => 'bool|WP_Error', - ], - ]; - - /** @var \PHPStan\PhpDoc\TypeStringResolver */ - protected $typeStringResolver; - - public function __construct(TypeStringResolver $typeStringResolver) - { - $this->typeStringResolver = $typeStringResolver; - } - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return array_key_exists($functionReflection->getName(), self::SUPPORTED_FUNCTIONS); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $name = $functionReflection->getName(); - $functionTypes = self::SUPPORTED_FUNCTIONS[$name] ?? null; - $args = $functionCall->getArgs(); - - if ($functionTypes === null) { - throw new \PHPStan\ShouldNotHappenException( - sprintf( - 'Could not detect return types for function %s()', - $name - ) - ); - } - - $wpErrorArgumentType = new ConstantBooleanType(false); - $wpErrorArgument = $args[$functionTypes['arg']] ?? null; - - if ($wpErrorArgument !== null) { - $wpErrorArgumentType = $scope->getType($wpErrorArgument->value); - } - - // When called with a $wp_error parameter that isn't a constant boolean, return default type - $type = $functionTypes['maybe']; - - if ($wpErrorArgumentType->isTrue()->yes()) { - $type = $functionTypes['true']; - } elseif ($wpErrorArgumentType->isFalse()->yes()) { - $type = $functionTypes['false']; - } - - return $this->typeStringResolver->resolve($type); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 5d0e9160..64c6a725 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -23,7 +23,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/get_sites.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_terms.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/shortcode_atts.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_error_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/wp_parse_url.php'); } diff --git a/tests/data/wp_error_parameter.php b/tests/data/wp_error_parameter.php deleted file mode 100644 index 63406725..00000000 --- a/tests/data/wp_error_parameter.php +++ /dev/null @@ -1,27 +0,0 @@ - Date: Thu, 20 Apr 2023 22:39:55 +0200 Subject: [PATCH 17/30] Merge get_comment extension into get_post extension --- extension.neon | 4 -- ...mentDynamicFunctionReturnTypeExtension.php | 47 ------------------- ...PostDynamicFunctionReturnTypeExtension.php | 12 ++++- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/get_comment.php | 16 ------- tests/data/get_post.php | 10 ++++ 6 files changed, 20 insertions(+), 70 deletions(-) delete mode 100644 src/GetCommentDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/get_comment.php diff --git a/extension.neon b/extension.neon index c5d2c737..75e246c8 100644 --- a/extension.neon +++ b/extension.neon @@ -39,10 +39,6 @@ services: class: SzepeViktor\PHPStan\WordPress\GetSitesDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\GetCommentDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\ShortcodeAttsDynamicFunctionReturnTypeExtension tags: diff --git a/src/GetCommentDynamicFunctionReturnTypeExtension.php b/src/GetCommentDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index e2f49744..00000000 --- a/src/GetCommentDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,47 +0,0 @@ -getName() === 'get_comment'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - $args = $functionCall->getArgs(); - - // When called with an instance of WP_Comment - if ( - count($args) > 0 && - (new ObjectType(WP_Comment::class))->isSuperTypeOf($scope->getType($args[0]->value))->yes() - ) { - return TypeCombinator::removeNull( - ParametersAcceptorSelector::selectFromArgs( - $scope, - $args, - $functionReflection->getVariants() - )->getReturnType() - ); - } - - return null; - } -} diff --git a/src/GetPostDynamicFunctionReturnTypeExtension.php b/src/GetPostDynamicFunctionReturnTypeExtension.php index f4ad3c8b..490f8346 100644 --- a/src/GetPostDynamicFunctionReturnTypeExtension.php +++ b/src/GetPostDynamicFunctionReturnTypeExtension.php @@ -16,12 +16,16 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use WP_Post; +use WP_Comment; class GetPostDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { + /** @var string */ + private $fullyQualifiedName = WP_Post::class; + public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return $functionReflection->getName() === 'get_post'; + return in_array($functionReflection->getName(), ['get_post', 'get_comment'], true); } /** @@ -31,10 +35,14 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, { $args = $functionCall->getArgs(); + if ($functionReflection->getName() === 'get_comment') { + $this->fullyQualifiedName = WP_Comment::class; + } + // When called with an instance of WP_Post if ( count($args) > 0 && - (new ObjectType(WP_Post::class))->isSuperTypeOf($scope->getType($args[0]->value))->yes() + (new ObjectType($this->fullyQualifiedName))->isSuperTypeOf($scope->getType($args[0]->value))->yes() ) { return TypeCombinator::removeNull( ParametersAcceptorSelector::selectFromArgs( diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 64c6a725..e40db292 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -17,7 +17,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_key.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/esc_sql.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/get_comment.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_posts.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_sites.php'); diff --git a/tests/data/get_comment.php b/tests/data/get_comment.php deleted file mode 100644 index 910d1b8f..00000000 --- a/tests/data/get_comment.php +++ /dev/null @@ -1,16 +0,0 @@ -', get_comment($comment, ARRAY_A)); -assertType('array', get_comment($comment, ARRAY_N)); -assertType('array|WP_Comment', get_comment($comment, $_GET['foo'])); diff --git a/tests/data/get_post.php b/tests/data/get_post.php index 0ace5792..d5eaba5f 100644 --- a/tests/data/get_post.php +++ b/tests/data/get_post.php @@ -13,3 +13,13 @@ assertType('WP_Post', get_post($wpPostType, OBJECT)); assertType('array', get_post($wpPostType, ARRAY_A)); assertType('array', get_post($wpPostType, ARRAY_N)); +assertType('array|WP_Post', get_post($wpPostType, $_GET['foo'])); + +/** @var \WP_Comment $comment */ +$comment = $comment; + +assertType('WP_Comment', get_comment($comment)); +assertType('WP_Comment', get_comment($comment, OBJECT)); +assertType('array', get_comment($comment, ARRAY_A)); +assertType('array', get_comment($comment, ARRAY_N)); +assertType('array|WP_Comment', get_comment($comment, $_GET['foo'])); From bd99d58acce13a58fe395be7fe85d4143aae98d0 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Fri, 21 Apr 2023 18:12:13 +0200 Subject: [PATCH 18/30] Remove type specifiying extension and rule for `is_wp_error()` --- README.md | 3 +- extension.neon | 5 - ...WpErrorFunctionTypeSpecifyingExtension.php | 47 ---------- src/IsWpErrorRule.php | 92 ------------------- tests/IsWpErrorRuleTest.php | 60 ------------ tests/data/get_post.php | 2 + tests/data/is_wp_error.php | 30 ------ 7 files changed, 3 insertions(+), 236 deletions(-) delete mode 100644 src/IsWpErrorFunctionTypeSpecifyingExtension.php delete mode 100644 src/IsWpErrorRule.php delete mode 100644 tests/IsWpErrorRuleTest.php delete mode 100644 tests/data/is_wp_error.php diff --git a/README.md b/README.md index 3ece780e..4259113e 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,8 @@ Please see [WooCommerce Stubs](https://github.com/php-stubs/woocommerce-stubs) - Makes it possible to run PHPStan on WordPress plugins and themes - Loads [`php-stubs/wordpress-stubs`](https://github.com/php-stubs/wordpress-stubs) package -- Provides dynamic return type extensions for many core functions +- Provides dynamic return type extensions for functions that are not covered in [`php-stubs/wordpress-stubs`](https://github.com/php-stubs/wordpress-stubs) - Defines some core constants -- Handles special functions and classes e.g. `is_wp_error()` - Validates the optional docblock that precedes a call to `apply_filters()` and treats the type of its first `@param` as certain ### Usage of an `apply_filters()` docblock diff --git a/extension.neon b/extension.neon index 75e246c8..013d8a53 100644 --- a/extension.neon +++ b/extension.neon @@ -1,8 +1,4 @@ services: - - - class: SzepeViktor\PHPStan\WordPress\IsWpErrorFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - class: SzepeViktor\PHPStan\WordPress\WpThemeMagicPropertiesClassReflectionExtension tags: @@ -70,7 +66,6 @@ services: rules: - SzepeViktor\PHPStan\WordPress\HookCallbackRule - SzepeViktor\PHPStan\WordPress\HookDocsRule - - SzepeViktor\PHPStan\WordPress\IsWpErrorRule parameters: bootstrapFiles: - ../../php-stubs/wordpress-stubs/wordpress-stubs.php diff --git a/src/IsWpErrorFunctionTypeSpecifyingExtension.php b/src/IsWpErrorFunctionTypeSpecifyingExtension.php deleted file mode 100644 index 3da27fae..00000000 --- a/src/IsWpErrorFunctionTypeSpecifyingExtension.php +++ /dev/null @@ -1,47 +0,0 @@ -getName()) === 'is_wp_error' - && isset($node->args[0]) - && !$context->null(); - } - - // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter - public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes - { - if ($context->null()) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $args = $node->getArgs(); - - return $this->typeSpecifier->create($args[0]->value, new ObjectType('WP_Error'), $context); - } - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } -} diff --git a/src/IsWpErrorRule.php b/src/IsWpErrorRule.php deleted file mode 100644 index 156bf1a7..00000000 --- a/src/IsWpErrorRule.php +++ /dev/null @@ -1,92 +0,0 @@ - - */ -class IsWpErrorRule implements \PHPStan\Rules\Rule -{ - /** @var \PHPStan\Rules\RuleLevelHelper */ - protected $ruleLevelHelper; - - public function __construct( - RuleLevelHelper $ruleLevelHelper - ) { - $this->ruleLevelHelper = $ruleLevelHelper; - } - - public function getNodeType(): string - { - return FuncCall::class; - } - - /** - * @param \PhpParser\Node\Expr\FuncCall $node - * @param \PHPStan\Analyser\Scope $scope - * @return array - */ - public function processNode(Node $node, Scope $scope): array - { - $name = $node->name; - - if (! ($name instanceof Name)) { - return []; - } - - if ($name->toString() !== 'is_wp_error') { - return []; - } - - $args = $node->getArgs(); - - if (! isset($args[0])) { - return []; - } - - $argumentType = $scope->getType($args[0]->value); - $accepted = $this->ruleLevelHelper->accepts( - $argumentType, - new ObjectType(\WP_Error::class), - $scope->isDeclareStrictTypes() - ); - - if (!$accepted) { - return [ - RuleErrorBuilder::message( - sprintf( - 'is_wp_error(%s) will always evaluate to false.', - $argumentType->describe(VerbosityLevel::typeOnly()) - ) - )->build(), - ]; - } - - if ((new ObjectType(\WP_Error::class))->isSuperTypeOf($argumentType)->yes()) { - return [ - RuleErrorBuilder::message( - 'is_wp_error(WP_Error) will always evaluate to true.' - )->build(), - ]; - } - - return []; - } -} diff --git a/tests/IsWpErrorRuleTest.php b/tests/IsWpErrorRuleTest.php deleted file mode 100644 index afce04c1..00000000 --- a/tests/IsWpErrorRuleTest.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ -class IsWpErrorRuleTest extends \PHPStan\Testing\RuleTestCase -{ - protected function getRule(): Rule - { - /** @var \PHPStan\Rules\RuleLevelHelper */ - $ruleLevelHelper = self::getContainer()->getByType(RuleLevelHelper::class); - - // getRule() method needs to return an instance of the tested rule - return new IsWpErrorRule($ruleLevelHelper); - } - - // phpcs:ignore NeutronStandard.Functions.LongFunction.LongFunction - public function testRule(): void - { - // first argument: path to the example file that contains some errors that should be reported by IsWpErrorRule - // second argument: an array of expected errors, - // each error consists of the asserted error message, and the asserted error file line - $this->analyse( - [ - __DIR__ . '/data/is_wp_error.php', - ], - [ - [ - 'is_wp_error(int) will always evaluate to false.', - 23, - ], - [ - 'is_wp_error(int|false) will always evaluate to false.', - 24, - ], - [ - 'is_wp_error(WP_Error) will always evaluate to true.', - 25, - ], - [ - 'is_wp_error(WP_Post) will always evaluate to false.', - 26, - ], - ] - ); - } - - public static function getAdditionalConfigFiles(): array - { - return [dirname(__DIR__) . '/vendor/szepeviktor/phpstan-wordpress/extension.neon']; - } -} diff --git a/tests/data/get_post.php b/tests/data/get_post.php index d5eaba5f..e8510e97 100644 --- a/tests/data/get_post.php +++ b/tests/data/get_post.php @@ -3,7 +3,9 @@ declare(strict_types=1); namespace SzepeViktor\PHPStan\WordPress\Tests; +use WP_Error; +use function is_wp_error; use function PHPStan\Testing\assertType; /** @var \WP_Post $wpPostType */ diff --git a/tests/data/is_wp_error.php b/tests/data/is_wp_error.php deleted file mode 100644 index 64cbca1d..00000000 --- a/tests/data/is_wp_error.php +++ /dev/null @@ -1,30 +0,0 @@ - Date: Fri, 21 Apr 2023 22:00:27 +0200 Subject: [PATCH 19/30] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Viktor Szépe --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4259113e..a557b908 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,8 @@ Please see [WooCommerce Stubs](https://github.com/php-stubs/woocommerce-stubs) - Makes it possible to run PHPStan on WordPress plugins and themes - Loads [`php-stubs/wordpress-stubs`](https://github.com/php-stubs/wordpress-stubs) package -- Provides dynamic return type extensions for functions that are not covered in [`php-stubs/wordpress-stubs`](https://github.com/php-stubs/wordpress-stubs) +- Provides dynamic return type extensions for functions + that are not covered in [`php-stubs/wordpress-stubs`](https://github.com/php-stubs/wordpress-stubs/blob/master/functionMap.php) - Defines some core constants - Validates the optional docblock that precedes a call to `apply_filters()` and treats the type of its first `@param` as certain From 9e904817320d303468e17664775783e9f859bafb Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Fri, 21 Apr 2023 22:01:27 +0200 Subject: [PATCH 20/30] Remove echo parameter extension --- extension.neon | 4 - ...eterDynamicFunctionReturnTypeExtension.php | 122 ------------------ tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/echo_parameter.php | 118 ----------------- 4 files changed, 245 deletions(-) delete mode 100644 src/EchoParameterDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/echo_parameter.php diff --git a/extension.neon b/extension.neon index 013d8a53..0ecec5bc 100644 --- a/extension.neon +++ b/extension.neon @@ -43,10 +43,6 @@ services: class: SzepeViktor\PHPStan\WordPress\ApplyFiltersDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\EchoParameterDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\WpParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/EchoParameterDynamicFunctionReturnTypeExtension.php b/src/EchoParameterDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index 7dfaa772..00000000 --- a/src/EchoParameterDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,122 +0,0 @@ - 2, - 'comment_class' => 3, - 'disabled' => 2, - 'edit_term_link' => 4, - 'get_calendar' => 1, - 'menu_page_url' => 1, - 'next_posts' => 1, - 'post_type_archive_title' => 1, - 'previous_posts' => 0, - 'selected' => 2, - 'single_cat_title' => 1, - 'single_month_title' => 1, - 'single_post_title' => 1, - 'single_tag_title' => 1, - 'single_term_title' => 1, - 'the_date' => 3, - 'the_modified_date' => 3, - 'the_title' => 2, - 'wp_loginout' => 1, - 'wp_nonce_field' => 3, - 'wp_original_referer_field' => 0, - 'wp_readonly' => 2, - 'wp_referer_field' => 0, - 'wp_register' => 2, - 'wp_title' => 1, - ]; - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return array_key_exists($functionReflection->getName(), self::SUPPORTED_FUNCTIONS); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - $name = $functionReflection->getName(); - $functionParameter = self::SUPPORTED_FUNCTIONS[$name] ?? null; - $args = $functionCall->getArgs(); - - if ($functionParameter === null) { - throw new \PHPStan\ShouldNotHappenException( - sprintf( - 'Could not detect return types for function %s()', - $name - ) - ); - } - - $echoArgumentType = new ConstantBooleanType(true); - - if (isset($args[$functionParameter])) { - $echoArgumentType = $scope->getType($args[$functionParameter]->value); - } - - if ($echoArgumentType->isTrue()->yes()) { - return self::getEchoTrueReturnType($name); - } - if ($echoArgumentType->isFalse()->yes()) { - return self::getEchoFalseReturnType($name); - } - - return TypeCombinator::union( - self::getEchoFalseReturnType($name), - self::getEchoTrueReturnType($name) - ); - } - - protected static function getEchoTrueReturnType(string $name): Type - { - if ($name === 'single_month_title') { - return TypeCombinator::union( - new VoidType(), - new ConstantBooleanType(false) - ); - } - - return new VoidType(); - } - - protected static function getEchoFalseReturnType(string $name): Type - { - if ($name === 'single_month_title') { - return TypeCombinator::union( - new StringType(), - new ConstantBooleanType(false) - ); - } - - if ($name === 'the_title') { - return TypeCombinator::union( - new StringType(), - new VoidType() - ); - } - - return new StringType(); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index e40db292..26dc7dcf 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -15,7 +15,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/_get_list_table.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/apply_filters.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_key.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_parameter.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/esc_sql.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/get_posts.php'); diff --git a/tests/data/echo_parameter.php b/tests/data/echo_parameter.php deleted file mode 100644 index 1c5e7ac2..00000000 --- a/tests/data/echo_parameter.php +++ /dev/null @@ -1,118 +0,0 @@ - Date: Thu, 27 Apr 2023 06:34:10 +0200 Subject: [PATCH 21/30] Remove _get_list_table extension --- extension.neon | 4 -- ...ableDynamicFunctionReturnTypeExtension.php | 50 ------------------- tests/DynamicReturnTypeExtensionTest.php | 1 - tests/data/_get_list_table.php | 14 ------ 4 files changed, 69 deletions(-) delete mode 100644 src/GetListTableDynamicFunctionReturnTypeExtension.php delete mode 100644 tests/data/_get_list_table.php diff --git a/extension.neon b/extension.neon index 0ecec5bc..0112bef9 100644 --- a/extension.neon +++ b/extension.neon @@ -7,10 +7,6 @@ services: class: SzepeViktor\PHPStan\WordPress\EchoKeyDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: SzepeViktor\PHPStan\WordPress\GetListTableDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\RedirectCanonicalDynamicFunctionReturnTypeExtension tags: diff --git a/src/GetListTableDynamicFunctionReturnTypeExtension.php b/src/GetListTableDynamicFunctionReturnTypeExtension.php deleted file mode 100644 index 6835093a..00000000 --- a/src/GetListTableDynamicFunctionReturnTypeExtension.php +++ /dev/null @@ -1,50 +0,0 @@ -getName() === '_get_list_table'; - } - - // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - $args = $functionCall->getArgs(); - - // Called without $class argument - if (count($args) < 1) { - return new ConstantBooleanType(false); - } - - $argumentType = $scope->getType($args[0]->value); - - // When called with a $class that isn't a constant string, return default return type - if (count($argumentType->getConstantStrings()) === 0) { - return null; - } - - $types = [new ConstantBooleanType(false)]; - foreach ($argumentType->getConstantStrings() as $constantString) { - $types[] = new ObjectType($constantString->getValue()); - } - - return TypeCombinator::union(...$types); - } -} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 26dc7dcf..12aea8d4 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -12,7 +12,6 @@ class DynamicReturnTypeExtensionTest extends \PHPStan\Testing\TypeInferenceTestC public function dataFileAsserts(): iterable { // Path to a file with actual asserts of expected types: - yield from $this->gatherAssertTypes(__DIR__ . '/data/_get_list_table.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/apply_filters.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_key.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/esc_sql.php'); diff --git a/tests/data/_get_list_table.php b/tests/data/_get_list_table.php deleted file mode 100644 index ee11bc32..00000000 --- a/tests/data/_get_list_table.php +++ /dev/null @@ -1,14 +0,0 @@ - Date: Fri, 16 Jun 2023 22:47:09 +0200 Subject: [PATCH 22/30] Revert "Remove _get_list_table extension" This reverts commit 0191253eca4bc5d0d3e42721934cf6d65744f89c. --- extension.neon | 4 ++ ...ableDynamicFunctionReturnTypeExtension.php | 50 +++++++++++++++++++ tests/DynamicReturnTypeExtensionTest.php | 1 + tests/data/_get_list_table.php | 14 ++++++ 4 files changed, 69 insertions(+) create mode 100644 src/GetListTableDynamicFunctionReturnTypeExtension.php create mode 100644 tests/data/_get_list_table.php diff --git a/extension.neon b/extension.neon index 0112bef9..0ecec5bc 100644 --- a/extension.neon +++ b/extension.neon @@ -7,6 +7,10 @@ services: class: SzepeViktor\PHPStan\WordPress\EchoKeyDynamicFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: SzepeViktor\PHPStan\WordPress\GetListTableDynamicFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension - class: SzepeViktor\PHPStan\WordPress\RedirectCanonicalDynamicFunctionReturnTypeExtension tags: diff --git a/src/GetListTableDynamicFunctionReturnTypeExtension.php b/src/GetListTableDynamicFunctionReturnTypeExtension.php new file mode 100644 index 00000000..6835093a --- /dev/null +++ b/src/GetListTableDynamicFunctionReturnTypeExtension.php @@ -0,0 +1,50 @@ +getName() === '_get_list_table'; + } + + // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type + { + $args = $functionCall->getArgs(); + + // Called without $class argument + if (count($args) < 1) { + return new ConstantBooleanType(false); + } + + $argumentType = $scope->getType($args[0]->value); + + // When called with a $class that isn't a constant string, return default return type + if (count($argumentType->getConstantStrings()) === 0) { + return null; + } + + $types = [new ConstantBooleanType(false)]; + foreach ($argumentType->getConstantStrings() as $constantString) { + $types[] = new ObjectType($constantString->getValue()); + } + + return TypeCombinator::union(...$types); + } +} diff --git a/tests/DynamicReturnTypeExtensionTest.php b/tests/DynamicReturnTypeExtensionTest.php index 12aea8d4..26dc7dcf 100644 --- a/tests/DynamicReturnTypeExtensionTest.php +++ b/tests/DynamicReturnTypeExtensionTest.php @@ -12,6 +12,7 @@ class DynamicReturnTypeExtensionTest extends \PHPStan\Testing\TypeInferenceTestC public function dataFileAsserts(): iterable { // Path to a file with actual asserts of expected types: + yield from $this->gatherAssertTypes(__DIR__ . '/data/_get_list_table.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/apply_filters.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_key.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/esc_sql.php'); diff --git a/tests/data/_get_list_table.php b/tests/data/_get_list_table.php new file mode 100644 index 00000000..ee11bc32 --- /dev/null +++ b/tests/data/_get_list_table.php @@ -0,0 +1,14 @@ + Date: Thu, 27 Jul 2023 19:28:21 +0200 Subject: [PATCH 23/30] Update get_post.php --- tests/data/get_post.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/data/get_post.php b/tests/data/get_post.php index e8510e97..d5eaba5f 100644 --- a/tests/data/get_post.php +++ b/tests/data/get_post.php @@ -3,9 +3,7 @@ declare(strict_types=1); namespace SzepeViktor\PHPStan\WordPress\Tests; -use WP_Error; -use function is_wp_error; use function PHPStan\Testing\assertType; /** @var \WP_Post $wpPostType */ From 25da5cbf07decad18f6e0db58dd8534dfa2aa0dd Mon Sep 17 00:00:00 2001 From: Der Mundschenk & Compagnie Date: Sat, 22 Apr 2023 00:38:17 +0200 Subject: [PATCH 24/30] Fix earlyTerminatingMethodCalls syntax (#173) --- extension.neon | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/extension.neon b/extension.neon index 0ecec5bc..e46df932 100644 --- a/extension.neon +++ b/extension.neon @@ -89,16 +89,16 @@ parameters: - wp_ajax_heartbeat - wp_ajax_nopriv_heartbeat earlyTerminatingMethodCalls: - \Custom_Background: - - Custom_Background::wp_set_background_image - \IXR_Server: - - IXR_Server::output - \WP_Ajax_Response: - - WP_Ajax_Response::send - \WP_CLI: - - WP_CLI::error - - WP_CLI::halt - \WP_Recovery_Mode: - - WP_Recovery_Mode::redirect_protected - \WP_Sitemaps_Stylesheet: - - WP_Sitemaps_Stylesheet::render_stylesheet + Custom_Background: + - wp_set_background_image + IXR_Server: + - output + WP_Ajax_Response: + - send + WP_CLI: + - error + - halt + WP_Recovery_Mode: + - redirect_protected + WP_Sitemaps_Stylesheet: + - render_stylesheet From e8abc32ccf4af677fda3a1de5418c9be0f81e671 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sat, 22 Apr 2023 00:36:50 +0200 Subject: [PATCH 25/30] Remove deprecated instanceof (#183) --- ...eUrlFunctionDynamicReturnTypeExtension.php | 33 ++++++++----------- tests/data/wp_parse_url.php | 22 +++++-------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/WpParseUrlFunctionDynamicReturnTypeExtension.php b/src/WpParseUrlFunctionDynamicReturnTypeExtension.php index c6ba7b8e..18abbd65 100644 --- a/src/WpParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/WpParseUrlFunctionDynamicReturnTypeExtension.php @@ -19,7 +19,6 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\ConstantType; use PHPStan\Type\IntegerType; use PHPStan\Type\NullType; use PHPStan\Type\StringType; @@ -63,32 +62,28 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $this->cacheReturnTypes(); - $urlType = $scope->getType($functionCall->getArgs()[0]->value); + $componentType = new ConstantIntegerType(-1); if (count($functionCall->getArgs()) > 1) { $componentType = $scope->getType($functionCall->getArgs()[1]->value); - if (!$componentType instanceof ConstantType) { - return $this->createAllComponentsReturnType(); - } - - $componentType = $componentType->toInteger(); - if (!$componentType instanceof ConstantIntegerType) { - throw new \PHPStan\ShouldNotHappenException(); + return $this->createAllComponentsReturnType(); } - } else { - $componentType = new ConstantIntegerType(-1); } - if ($urlType instanceof ConstantStringType) { - try { - // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $result = @parse_url($urlType->getValue(), $componentType->getValue()); - } catch (\ValueError $e) { - return new ConstantBooleanType(false); + $urlType = $scope->getType($functionCall->getArgs()[0]->value); + if (count($urlType->getConstantStrings()) !== 0) { + $returnTypes = []; + foreach ($urlType->getConstantStrings() as $constantString) { + try { + // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged + $result = @parse_url($constantString->getValue(), $componentType->getValue()); + $returnTypes[] = $scope->getTypeFromValue($result); + } catch (\ValueError $e) { + $returnTypes[] = new ConstantBooleanType(false); + } } - - return $scope->getTypeFromValue($result); + return TypeCombinator::union(...$returnTypes); } if ($componentType->getValue() === -1) { diff --git a/tests/data/wp_parse_url.php b/tests/data/wp_parse_url.php index 3e192744..4ad14909 100644 --- a/tests/data/wp_parse_url.php +++ b/tests/data/wp_parse_url.php @@ -8,20 +8,8 @@ namespace SzepeViktor\PHPStan\WordPress\Tests; +use function wp_parse_url; use function PHPStan\Testing\assertType; -use function wp_clear_scheduled_hook; -use function wp_insert_attachment; -use function wp_insert_category; -use function wp_insert_link; -use function wp_insert_post; -use function wp_reschedule_event; -use function wp_schedule_event; -use function wp_schedule_single_event; -use function wp_set_comment_status; -use function wp_unschedule_event; -use function wp_unschedule_hook; -use function wp_update_comment; -use function wp_update_post; /** @var int $integer */ $integer = doFoo(); @@ -61,3 +49,11 @@ $value = wp_parse_url($string); assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $value); + +/** @var 'http://def.abc'|'https://example.com' $union */ +$union = $union; +assertType("array{scheme: 'http', host: 'def.abc'}|array{scheme: 'https', host: 'example.com'}", wp_parse_url($union)); + +/** @var 'http://def.abc#fragment1'|'https://example.com#fragment2' $union */ +$union = $union; +assertType("'fragment1'|'fragment2'", wp_parse_url($union, PHP_URL_FRAGMENT)); From ce4e3d32e3c99f20435dca4cac373b365579b6f4 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sun, 23 Apr 2023 07:43:23 +0200 Subject: [PATCH 26/30] Remove deprecated `instanceof` (#184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove deprecated `instanceof` * Remove space Co-authored-by: Viktor Szépe * Return early * Fix handling of fields unions * Add tests --------- Co-authored-by: Viktor Szépe --- ...ostsDynamicFunctionReturnTypeExtension.php | 85 ++++++++++++------- tests/data/get_posts.php | 56 +++++++++++- 2 files changed, 111 insertions(+), 30 deletions(-) diff --git a/src/GetPostsDynamicFunctionReturnTypeExtension.php b/src/GetPostsDynamicFunctionReturnTypeExtension.php index 79baaee9..27cf20aa 100644 --- a/src/GetPostsDynamicFunctionReturnTypeExtension.php +++ b/src/GetPostsDynamicFunctionReturnTypeExtension.php @@ -8,6 +8,7 @@ namespace SzepeViktor\PHPStan\WordPress; +use WP_Post; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; @@ -15,9 +16,11 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\IntegerType; use PHPStan\Type\ObjectType; -use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\Constant\ConstantStringType; +use function count; + class GetPostsDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool @@ -36,43 +39,67 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, // Called without arguments if (count($args) === 0) { - return new ArrayType(new IntegerType(), new ObjectType('WP_Post')); + return self::getObjectType(); } $argumentType = $scope->getType($args[0]->value); + $returnTypes = []; - // Called with an array argument - if ($argumentType instanceof ConstantArrayType) { - foreach ($argumentType->getKeyTypes() as $index => $key) { - if (! $key instanceof ConstantStringType || $key->getValue() !== 'fields') { - continue; - } + if ($argumentType->isConstantArray()->no()) { + return self::getIndeterminedType(); + } + + if ($argumentType->isConstantArray()->maybe()) { + $returnTypes[] = self::getIndeterminedType(); + } + + foreach ($argumentType->getConstantArrays() as $array) { + if ($array->hasOffsetValueType(new ConstantStringType('fields'))->no()) { + $returnTypes[] = self::getObjectType(); + continue; + } + + if ($array->hasOffsetValueType(new ConstantStringType('fields'))->maybe()) { + $returnTypes[] = self::getIndeterminedType(); + continue; + } + + $fieldsValueTypes = $array->getOffsetValueType(new ConstantStringType('fields'))->getConstantStrings(); + + if (count($fieldsValueTypes) === 0) { + $returnTypes[] = self::getIndeterminedType(); + continue; + } - $fieldsType = $argumentType->getValueTypes()[$index]; - if ($fieldsType instanceof ConstantStringType) { - $fields = $fieldsType->getValue(); + foreach ($fieldsValueTypes as $fieldsValueType) { + switch ($fieldsValueType->getValue()) { + case 'id=>parent': + case 'ids': + $returnTypes[] = self::getIntType(); + break; + default: + $returnTypes[] = self::getObjectType(); } - break; } } - // Called with a string argument - if ($argumentType instanceof ConstantStringType) { - parse_str($argumentType->getValue(), $variables); - $fields = $variables['fields'] ?? 'all'; - } + return TypeCombinator::union(...$returnTypes); + } - // Without constant argument return default return type - if (! isset($fields)) { - return new ArrayType(new IntegerType(), new ObjectType('WP_Post')); - } + private static function getIntType(): Type + { + return new ArrayType(new IntegerType(), new IntegerType()); + } - switch ($fields) { - case 'id=>parent': - case 'ids': - return new ArrayType(new IntegerType(), new IntegerType()); - case 'all': - default: - return new ArrayType(new IntegerType(), new ObjectType('WP_Post')); - } + private static function getObjectType(): Type + { + return new ArrayType(new IntegerType(), new ObjectType(WP_Post::class)); + } + + private static function getIndeterminedType(): Type + { + return TypeCombinator::union( + new ArrayType(new IntegerType(), new ObjectType(WP_Post::class)), + new ArrayType(new IntegerType(), new IntegerType()), + ); } } diff --git a/tests/data/get_posts.php b/tests/data/get_posts.php index 9de82bec..1990ae57 100644 --- a/tests/data/get_posts.php +++ b/tests/data/get_posts.php @@ -7,6 +7,60 @@ use function PHPStan\Testing\assertType; assertType('array', get_posts()); -assertType('array', get_posts(['fields' => 'all'])); +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'])); From abbf8dd9f4a17c9f437bfa26a276509650408a8f Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sun, 23 Apr 2023 07:43:40 +0200 Subject: [PATCH 27/30] Remove deprecated `instanceof` (#185) --- ...ermsDynamicFunctionReturnTypeExtension.php | 71 ++++++--------- tests/data/get_terms.php | 91 +++++++++++++++---- 2 files changed, 97 insertions(+), 65 deletions(-) diff --git a/src/GetTermsDynamicFunctionReturnTypeExtension.php b/src/GetTermsDynamicFunctionReturnTypeExtension.php index 9909df35..b83e305f 100644 --- a/src/GetTermsDynamicFunctionReturnTypeExtension.php +++ b/src/GetTermsDynamicFunctionReturnTypeExtension.php @@ -16,10 +16,10 @@ use PHPStan\Type\IntegerType; use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; -use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\ConstantScalarType; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Constant\ConstantStringType; + class GetTermsDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { @@ -61,27 +61,35 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $argument = $scope->getType($functionCall->getArgs()[$argsParameterPosition]->value); + $returnTypes = []; - if (!($argument instanceof ConstantArrayType)) { - // Without constant array argument return default return type + if ($argument->isConstantArray()->no()) { return self::defaultType(); } - - $args = self::getArgs($argument); - - if ($args === null) { - return self::defaultType(); + if ($argument->isConstantArray()->maybe()) { + $returnTypes[] = self::defaultType(); } - if (isset($args['count']) && $args['count'] === true) { - return self::countType(); - } + foreach ($argument->getConstantArrays() as $args) { + if ($args->hasOffsetValueType(new ConstantStringType('fields'))->no()) { + $returnTypes[] = self::termsType(); + continue; + } + if ($args->hasOffsetValueType(new ConstantStringType('fields'))->maybe()) { + $returnTypes[] = self::defaultType(); + continue; + } - if (! isset($args['fields'], $args['count']) || ! is_string($args['fields'])) { - return self::defaultType(); + $fieldsValueTypes = $args->getOffsetValueType(new ConstantStringType('fields'))->getConstantStrings(); + if (count($fieldsValueTypes) === 0) { + $returnTypes[] = self::defaultType(); + continue; + } + foreach ($fieldsValueTypes as $fieldsValueType) { + $returnTypes[] = self::deduceTypeFromFields($fieldsValueType->getValue()); + } } - - return self::deduceTypeFromFields($args['fields']); + return TypeCombinator::union(...$returnTypes); } protected static function deduceTypeFromFields(string $fields): Type @@ -106,33 +114,6 @@ protected static function deduceTypeFromFields(string $fields): Type } } - /** - * @return array - */ - protected static function getArgs(ConstantArrayType $argument): ?array - { - $args = [ - 'fields' => 'all', - 'count' => false, - ]; - - foreach ($argument->getKeyTypes() as $index => $key) { - if (! $key instanceof ConstantStringType) { - return null; - } - - unset($args[$key->getValue()]); - $fieldsType = $argument->getValueTypes()[$index]; - if (!($fieldsType instanceof ConstantScalarType)) { - continue; - } - - $args[$key->getValue()] = $fieldsType->getValue(); - } - - return $args; - } - protected static function countType(): Type { return TypeCombinator::union( @@ -160,7 +141,7 @@ protected static function idsType(): Type protected static function parentsType(): Type { return TypeCombinator::union( - new ArrayType(new IntegerType(), new StringType()), + new ArrayType(new IntegerType(), new AccessoryNumericStringType()), new ObjectType('WP_Error') ); } diff --git a/tests/data/get_terms.php b/tests/data/get_terms.php index 450aebf4..b489e0a0 100644 --- a/tests/data/get_terms.php +++ b/tests/data/get_terms.php @@ -6,32 +6,13 @@ use function PHPStan\Testing\assertType; -$fields = $_GET['fields'] ?? 'all'; -$key = $_GET['key'] ?? 'fields'; -$count = ! empty( $_GET['count'] ); - // Default argument values assertType('array|WP_Error', get_terms()); assertType('array|WP_Error', get_terms([])); -// Unknown values -assertType('array|string|WP_Error', get_terms(['fields'=>$fields])); -assertType('array|string|WP_Error', get_terms(['foo'=>'bar','fields'=>$fields])); -assertType('array|string|WP_Error', get_terms(['count'=>$count])); -assertType('array|string|WP_Error', get_terms(['count'=>$count,'fields'=>'ids'])); -assertType('array|string|WP_Error', get_terms(['fields'=>$fields,'count'=>false])); - -// Unknown keys -assertType('array|string|WP_Error', get_terms([$key=>'all'])); -assertType('array|string|WP_Error', get_terms(['foo'=>'bar',$key=>'all'])); - // Requesting a count assertType('string|WP_Error', get_terms(['fields'=>'count'])); assertType('string|WP_Error', get_terms(['foo'=>'bar','fields'=>'count'])); -assertType('string|WP_Error', get_terms(['count'=>true])); -assertType('string|WP_Error', get_terms(['foo'=>'bar','count'=>true])); -assertType('string|WP_Error', get_terms(['fields'=>'ids','count'=>true])); -assertType('string|WP_Error', get_terms(['fields'=>$fields,'count'=>true])); // Requesting names or slugs assertType('array|WP_Error', get_terms(['fields'=>'names'])); @@ -44,7 +25,7 @@ assertType('array|WP_Error', get_terms(['fields'=>'tt_ids'])); // Requesting parent IDs (numeric strings) -assertType('array|WP_Error', get_terms(['fields'=>'id=>parent'])); +assertType('array|WP_Error', get_terms(['fields'=>'id=>parent'])); // Requesting objects assertType('array|WP_Error', get_terms(['fields'=>'all'])); @@ -60,3 +41,73 @@ assertType('array|WP_Error', wp_get_post_categories(123)); assertType('array|WP_Error', wp_get_post_tags(123)); assertType('array|WP_Error', wp_get_post_terms(123, 'category')); + +// Unions - nonexhaustive list +$fields = $_GET['foo'] ? (string)$_GET['string'] : 'all'; +$key = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +$count = ! empty( $_GET['count'] ); + +$union = $_GET['foo'] ? ['key'=>'value'] : ['some'=>'thing']; +assertType('array|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['key'=>'value'] : ['fields'=>'count']; +assertType('array|string|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['key'=>'value'] : ['fields'=>'names']; +assertType('array|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['key'=>'value'] : ['fields'=>'ids']; +assertType('array|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['key'=>'value'] : ['fields'=>'id=>parent']; +assertType('array|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['key'=>'value'] : ['fields'=>'all']; +assertType('array|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['fields'=>'names'] : ['fields'=>'count']; +assertType('array|string|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['fields'=>'id=>parent'] : ['fields'=>'ids']; +assertType('array|WP_Error', get_terms($union)); + +$union = $_GET['foo'] ? ['fields'=>'all'] : ['fields'=>'count']; +assertType('array|string|WP_Error', get_terms($union)); + +// Unions with unknown fields value +$union = $_GET['foo'] ? (string)$_GET['string'] : 'all'; +assertType('array|string|WP_Error', get_terms(['fields'=>$union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'ids'; +assertType('array|string|WP_Error', get_terms(['fields'=>$union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'id=>parent'; +assertType('array|string|WP_Error', get_terms(['fields'=>$union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'count'; +assertType('array|string|WP_Error', get_terms(['fields'=>$union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'names'; +assertType('array|string|WP_Error', get_terms(['fields'=>$union])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'all'; +assertType('array|string|WP_Error', get_terms(['foo'=>'bar','fields'=>$union])); + +// Unions with unknown fields key - not supported +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array|string|WP_Error', get_terms([$union=>'all'])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array|string|WP_Error', get_terms([$union=>'ids'])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array|string|WP_Error', get_terms([$union=>'id=>parent'])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array|string|WP_Error', get_terms([$union=>'count'])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array|string|WP_Error', get_terms([$union=>'names'])); + +$union = $_GET['foo'] ? (string)$_GET['string'] : 'fields'; +assertType('array|string|WP_Error', get_terms(['foo'=>'bar',$union=>'all'])); From 3eedadd73ff97d08cabaeba04ba4737f65dc25b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sun, 23 Apr 2023 08:15:06 +0200 Subject: [PATCH 28/30] Fix CS (#186) * Fix CS * Fix PHP 7.2 compat. --- src/GetPostsDynamicFunctionReturnTypeExtension.php | 2 +- src/GetTermsDynamicFunctionReturnTypeExtension.php | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/GetPostsDynamicFunctionReturnTypeExtension.php b/src/GetPostsDynamicFunctionReturnTypeExtension.php index 27cf20aa..d25ff24c 100644 --- a/src/GetPostsDynamicFunctionReturnTypeExtension.php +++ b/src/GetPostsDynamicFunctionReturnTypeExtension.php @@ -99,7 +99,7 @@ private static function getIndeterminedType(): Type { return TypeCombinator::union( new ArrayType(new IntegerType(), new ObjectType(WP_Post::class)), - new ArrayType(new IntegerType(), new IntegerType()), + new ArrayType(new IntegerType(), new IntegerType()) ); } } diff --git a/src/GetTermsDynamicFunctionReturnTypeExtension.php b/src/GetTermsDynamicFunctionReturnTypeExtension.php index b83e305f..621b6737 100644 --- a/src/GetTermsDynamicFunctionReturnTypeExtension.php +++ b/src/GetTermsDynamicFunctionReturnTypeExtension.php @@ -20,7 +20,6 @@ use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantStringType; - class GetTermsDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { private const SUPPORTED_FUNCTIONS = [ @@ -40,6 +39,8 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo /** * @see https://developer.wordpress.org/reference/functions/get_terms/ * @see https://developer.wordpress.org/reference/classes/wp_term_query/__construct/ + * + * phpcs:ignore NeutronStandard.Functions.LongFunction.LongFunction */ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { @@ -48,10 +49,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if ($argsParameterPosition === null) { throw new \PHPStan\ShouldNotHappenException( - sprintf( - 'Could not detect parameter position for function %s()', - $name - ) + sprintf('Could not detect parameter position for function %s()', $name) ); } From de4be2760fa4db3155425b2091bc1b8f04846848 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Sat, 17 Jun 2023 00:09:56 +0200 Subject: [PATCH 29/30] Fix _get_list_table extension (#190) --- ...ableDynamicFunctionReturnTypeExtension.php | 28 +++++++++++++++++-- tests/data/_get_list_table.php | 5 ++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/GetListTableDynamicFunctionReturnTypeExtension.php b/src/GetListTableDynamicFunctionReturnTypeExtension.php index 6835093a..26dcbc6c 100644 --- a/src/GetListTableDynamicFunctionReturnTypeExtension.php +++ b/src/GetListTableDynamicFunctionReturnTypeExtension.php @@ -18,6 +18,26 @@ class GetListTableDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { + private const CORE_CLASSES = [ + 'WP_Posts_List_Table', + 'WP_Media_List_Table', + 'WP_Terms_List_Table', + 'WP_Users_List_Table', + 'WP_Comments_List_Table', + 'WP_Post_Comments_List_Table', + 'WP_Links_List_Table', + 'WP_Plugin_Install_List_Table', + 'WP_Themes_List_Table', + 'WP_Theme_Install_List_Table', + 'WP_Plugins_List_Table', + 'WP_Application_Passwords_List_Table', + 'WP_MS_Sites_List_Table', + 'WP_MS_Users_List_Table', + 'WP_MS_Themes_List_Table', + 'WP_Privacy_Data_Export_Requests_List_Table', + 'WP_Privacy_Data_Removal_Requests_List_Table', + ]; + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === '_get_list_table'; @@ -30,7 +50,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, // Called without $class argument if (count($args) < 1) { - return new ConstantBooleanType(false); + return null; } $argumentType = $scope->getType($args[0]->value); @@ -40,9 +60,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return null; } - $types = [new ConstantBooleanType(false)]; + $types = []; foreach ($argumentType->getConstantStrings() as $constantString) { - $types[] = new ObjectType($constantString->getValue()); + $types[] = in_array($constantString->getValue(), self::CORE_CLASSES, true) + ? new ObjectType($constantString->getValue()) + : new ConstantBooleanType(false); } return TypeCombinator::union(...$types); diff --git a/tests/data/_get_list_table.php b/tests/data/_get_list_table.php index ee11bc32..88b48411 100644 --- a/tests/data/_get_list_table.php +++ b/tests/data/_get_list_table.php @@ -4,11 +4,12 @@ namespace SzepeViktor\PHPStan\WordPress\Tests; +use function _get_list_table; use function PHPStan\Testing\assertType; // Known class name -assertType('WP_Posts_List_Table|false', _get_list_table('WP_Posts_List_Table')); -assertType('Unknown_Table|false', _get_list_table('Unknown_Table')); +assertType('WP_Posts_List_Table', _get_list_table('WP_Posts_List_Table')); +assertType('false', _get_list_table('Unknown_Table')); // Unknown class name assertType('WP_List_Table|false', _get_list_table($_GET['foo'])); From 886a0668ec6473b3f32a6b15c471e4f2ae50fca8 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 27 Jul 2023 20:07:52 +0200 Subject: [PATCH 30/30] Revert "Update composer.json" This reverts commit d15b7e138fe3c1059458c397f563a69ec5fb9282. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e903adfd..9d7e0fde 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "php-stubs/wordpress-stubs": "dev-master", + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", "phpstan/phpstan": "^1.10.0", "symfony/polyfill-php73": "^1.12.0" },