diff --git a/extension.neon b/extension.neon index 494261774..44c094fd9 100644 --- a/extension.neon +++ b/extension.neon @@ -11,6 +11,14 @@ services: class: WP_CLI\Tests\PHPStan\WPCliRuncommandDynamicReturnTypeExtension tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: WP_CLI\Tests\PHPStan\WPCliDoHookDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: SzepeViktor\PHPStan\WordPress\HookDocsVisitor + tags: + - phpstan.parser.richParserNodeVisitor parameters: dynamicConstantNames: - FOO diff --git a/src/PHPStan/WPCliDoHookDynamicReturnTypeExtension.php b/src/PHPStan/WPCliDoHookDynamicReturnTypeExtension.php new file mode 100644 index 000000000..ef1c86d0e --- /dev/null +++ b/src/PHPStan/WPCliDoHookDynamicReturnTypeExtension.php @@ -0,0 +1,69 @@ +fileTypeMapper = $fileTypeMapper; + } + + public function getClass(): string { + return 'WP_CLI'; + } + + public function isStaticMethodSupported( MethodReflection $methodReflection ): bool { + return $methodReflection->getName() === 'do_hook'; + } + + public function getTypeFromStaticMethodCall( + MethodReflection $methodReflection, + StaticCall $methodCall, + Scope $scope + ): Type { + $args = $methodCall->getArgs(); + + if ( count( $args ) < 2 ) { + return new NullType(); + } + + $docComment = $methodCall->getAttribute( 'latestDocComment' ); + if ( $docComment instanceof Doc ) { + $classReflection = $scope->getClassReflection(); + $traitReflection = $scope->getTraitReflection(); + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + ( $scope->isInClass() && null !== $classReflection ) ? $classReflection->getName() : null, + ( $scope->isInTrait() && null !== $traitReflection ) ? $traitReflection->getName() : null, + $scope->getFunctionName(), + $docComment->getText() + ); + + $params = $resolvedPhpDoc->getParamTags(); + foreach ( $params as $paramTag ) { + return $paramTag->getType(); + } + } + + return $scope->getType( $args[1]->value ); + } +} diff --git a/tests/data/do_hook.php b/tests/data/do_hook.php new file mode 100644 index 000000000..508a2d20e --- /dev/null +++ b/tests/data/do_hook.php @@ -0,0 +1,49 @@ + 1, + 'b' => 2, +]; +$array_result = WP_CLI::do_hook( 'custom_array_filter', $array_data ); +assertType( 'array{a: 1, b: 2}', $array_result ); + +/** @var array $typed_array */ +$typed_array = [ 'count' => 5 ]; +$typed_result = WP_CLI::do_hook( 'custom_typed_filter', $typed_array ); +assertType( 'array', $typed_result ); + +/** + * Filter available options. + * + * @param array $options Filtered options. + */ +$docblock_result = WP_CLI::do_hook( 'custom_docblock_filter', $typed_array ); +assertType( 'array', $docblock_result ); + +/** + * Filter available formats. + * + * @param string[] $formats Array of format names. + */ +$formats_result = WP_CLI::do_hook( 'formatter_available_formats', [ 'table', 'json' ] ); +assertType( 'array', $formats_result ); diff --git a/tests/tests/PHPStan/TestDynamicReturnTypeExtension.php b/tests/tests/PHPStan/TestDynamicReturnTypeExtension.php index 801d87527..5c4e74672 100644 --- a/tests/tests/PHPStan/TestDynamicReturnTypeExtension.php +++ b/tests/tests/PHPStan/TestDynamicReturnTypeExtension.php @@ -16,6 +16,7 @@ public static function dataFileAsserts(): iterable { yield from self::gatherAssertTypes( dirname( __DIR__, 2 ) . '/data/parse_url.php' ); yield from self::gatherAssertTypes( dirname( __DIR__, 2 ) . '/data/get_flag_value.php' ); yield from self::gatherAssertTypes( dirname( __DIR__, 2 ) . '/data/runcommand.php' ); + yield from self::gatherAssertTypes( dirname( __DIR__, 2 ) . '/data/do_hook.php' ); } /**