diff --git a/composer.json b/composer.json index e71b605..a690434 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "require-dev": { "php": "~7.3 || ~8.0", "nikic/php-parser": "< 4.12.0", - "php-stubs/generator": "^0.8.0", + "php-stubs/generator": "dev-master", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpstan": "^1.2" }, diff --git a/functionMap.php b/functionMap.php new file mode 100644 index 0000000..b5df5d6 --- /dev/null +++ b/functionMap.php @@ -0,0 +1,30 @@ +, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error'; + +/** + * This array is in the same format as the function map array in PHPStan: + * + * '' => [', ''=>''] + * + * @link https://github.com/phpstan/phpstan-src/blob/1.5.x/resources/functionMap.php + */ +return [ + 'add_meta_box' => ['void', 'context'=>'"normal"|"side"|"advanced"', 'priority'=>'"high"|"core"|"default"|"low"'], + 'remove_meta_box' => ['void', 'context'=>'"normal"|"side"|"advanced"'], + 'WP_Http::get' => [$httpReturnType], + 'WP_Http::head' => [$httpReturnType], + 'WP_Http::post' => [$httpReturnType], + 'WP_Http::request' => [$httpReturnType], + 'WP_List_Table::bulk_actions' => ['void', 'which'=>'"top"|"bottom"'], + 'WP_List_Table::display_tablenav' => ['void', 'which'=>'"top"|"bottom"'], + 'WP_List_Table::pagination' => ['void', 'which'=>'"top"|"bottom"'], + 'wp_remote_get' => [$httpReturnType], + 'wp_remote_head' => [$httpReturnType], + 'wp_remote_post' => [$httpReturnType], + 'wp_remote_request' => [$httpReturnType], + 'wp_safe_remote_get' => [$httpReturnType], + 'wp_safe_remote_head' => [$httpReturnType], + 'wp_safe_remote_post' => [$httpReturnType], + 'wp_safe_remote_request' => [$httpReturnType], +]; diff --git a/visitor.php b/visitor.php index 54456c9..97ef96a 100644 --- a/visitor.php +++ b/visitor.php @@ -19,6 +19,16 @@ */ private $docBlockFactory; + /** + * @var ?array> + */ + private $functionMap = null; + + /** + * @var string + */ + private $currentSymbolName; + public function __construct() { $this->docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance(); @@ -38,12 +48,39 @@ public function enterNode(Node $node) return null; } + $this->currentSymbolName = $node->name->name; + + if ($node instanceof ClassMethod) { + /** @var \PhpParser\Node\Stmt\Class_ $parent */ + $parent = $this->stack[count($this->stack) - 2]; + + if (isset($parent->name)) { + $this->currentSymbolName = sprintf( + '%1$s::%2$s', + $parent->name->name, + $node->name->name + ); + } + } + $newDocComment = $this->addArrayHashNotation($docComment); if ($newDocComment !== null) { $node->setDocComment($newDocComment); } + $docComment = $node->getDocComment(); + + if (!($docComment instanceof Doc)) { + return null; + } + + $newDocComment = $this->addAdditionalParams($docComment); + + if ($newDocComment !== null) { + $node->setDocComment($newDocComment); + } + return null; } @@ -100,6 +137,43 @@ private function addArrayHashNotation(Doc $docComment): ?Doc return new Doc($newDocComment, $docComment->getLine(), $docComment->getFilePos()); } + private function addAdditionalParams(Doc $docComment): ?Doc + { + if (! isset($this->functionMap)) { + $this->functionMap = require __DIR__ . '/functionMap.php'; + } + + if (! isset($this->functionMap[$this->currentSymbolName])) { + return null; + } + + $parameters = $this->functionMap[$this->currentSymbolName]; + $returnType = array_shift($parameters); + $additions = []; + + foreach ($parameters as $paramName => $paramType) { + $additions[] = sprintf( + '@phpstan-param %s $%s', + $paramType, + $paramName + ); + } + + $additions[] = sprintf( + '@phpstan-return %s', + $returnType + ); + + $docCommentText = $docComment->getText(); + $newDocComment = sprintf( + "%s\n * %s\n */", + substr($docCommentText, 0, -4), + implode("\n * ", $additions) + ); + + return new Doc($newDocComment, $docComment->getLine(), $docComment->getFilePos()); + } + private function getAdditionFromParam(Param $tag): ?string { $tagDescription = $tag->getDescription(); diff --git a/wordpress-stubs.php b/wordpress-stubs.php index 9587907..37efea0 100644 --- a/wordpress-stubs.php +++ b/wordpress-stubs.php @@ -4290,6 +4290,8 @@ protected function get_bulk_actions() * * @param string $which The location of the bulk actions: 'top' or 'bottom'. * This is designated as optional for backward compatibility. + * @phpstan-param "top"|"bottom" $which + * @phpstan-return void */ protected function bulk_actions($which = '') { @@ -4378,6 +4380,8 @@ protected function get_items_per_page($option, $default = 20) * @since 3.1.0 * * @param string $which + * @phpstan-param "top"|"bottom" $which + * @phpstan-return void */ protected function pagination($which) { @@ -4494,6 +4498,8 @@ protected function get_table_classes() * * @since 3.1.0 * @param string $which + * @phpstan-param "top"|"bottom" $which + * @phpstan-return void */ protected function display_tablenav($which) { @@ -40697,6 +40703,7 @@ class WP_Http * filename?: string, * limit_response_size?: int, * } $args + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ public function request($url, $args = array()) { @@ -40785,6 +40792,7 @@ private function _dispatch_request($url, $args) * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ public function post($url, $args = array()) { @@ -40800,6 +40808,7 @@ public function post($url, $args = array()) * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ public function get($url, $args = array()) { @@ -40815,6 +40824,7 @@ public function get($url, $args = array()) * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ public function head($url, $args = array()) { @@ -81891,6 +81901,9 @@ function wp_import_upload_form($action) * @param array $callback_args Optional. Data that should be set as the $args property * of the box array (which is the second parameter passed * to your callback). Default null. + * @phpstan-param "normal"|"side"|"advanced" $context + * @phpstan-param "high"|"core"|"default"|"low" $priority + * @phpstan-return void */ function add_meta_box($id, $title, $callback, $screen = \null, $context = 'advanced', $priority = 'default', $callback_args = \null) { @@ -81970,6 +81983,8 @@ function do_meta_boxes($screen, $context, $object) * include 'normal', 'side', and 'advanced'. Comments screen contexts * include 'normal' and 'side'. Menus meta boxes (accordion sections) * all use the 'side' context. + * @phpstan-param "normal"|"side"|"advanced" $context + * @phpstan-return void */ function remove_meta_box($id, $screen, $context) { @@ -104003,6 +104018,7 @@ function _wp_http_get_object() * @param string $url URL to retrieve. * @param array $args Optional. Request arguments. Default empty array. * @return array|WP_Error The response or WP_Error on failure. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_safe_remote_request($url, $args = array()) { @@ -104021,6 +104037,7 @@ function wp_safe_remote_request($url, $args = array()) * @param string $url URL to retrieve. * @param array $args Optional. Request arguments. Default empty array. * @return array|WP_Error The response or WP_Error on failure. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_safe_remote_get($url, $args = array()) { @@ -104039,6 +104056,7 @@ function wp_safe_remote_get($url, $args = array()) * @param string $url URL to retrieve. * @param array $args Optional. Request arguments. Default empty array. * @return array|WP_Error The response or WP_Error on failure. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_safe_remote_post($url, $args = array()) { @@ -104057,6 +104075,7 @@ function wp_safe_remote_post($url, $args = array()) * @param string $url URL to retrieve. * @param array $args Optional. Request arguments. Default empty array. * @return array|WP_Error The response or WP_Error on failure. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_safe_remote_head($url, $args = array()) { @@ -104097,6 +104116,7 @@ function wp_safe_remote_head($url, $args = array()) * cookies: WP_HTTP_Cookie[], * http_response: WP_HTTP_Requests_Response|null, * } + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_remote_request($url, $args = array()) { @@ -104112,6 +104132,7 @@ function wp_remote_request($url, $args = array()) * @param string $url URL to retrieve. * @param array $args Optional. Request arguments. Default empty array. * @return array|WP_Error The response or WP_Error on failure. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_remote_get($url, $args = array()) { @@ -104127,6 +104148,7 @@ function wp_remote_get($url, $args = array()) * @param string $url URL to retrieve. * @param array $args Optional. Request arguments. Default empty array. * @return array|WP_Error The response or WP_Error on failure. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_remote_post($url, $args = array()) { @@ -104142,6 +104164,7 @@ function wp_remote_post($url, $args = array()) * @param string $url URL to retrieve. * @param array $args Optional. Request arguments. Default empty array. * @return array|WP_Error The response or WP_Error on failure. + * @phpstan-return array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error */ function wp_remote_head($url, $args = array()) {