From 09d67ed8097d06d261ad3fe7a6d02c094cc5ec30 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:13:35 +0200 Subject: [PATCH 1/7] Add conditional return type for term_exists() --- functionMap62.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functionMap62.php b/functionMap62.php index d48cb99e..6a5d89b8 100644 --- a/functionMap62.php +++ b/functionMap62.php @@ -89,5 +89,5 @@ 'get_post_permalink' => ['($post is \WP_Post ? string : string|false)'], 'term_exists' => ["(\$term is 0 ? 0 : (\$term is '' ? null : (\$taxonomy is '' ? string|null : array{term_id: string, term_taxonomy_id: string}|null)))"], 'is_term' => ["(\$term is 0 ? 0 : (\$term is '' ? null : (\$taxonomy is '' ? string|null : array{term_id: string, term_taxonomy_id: string}|null)))"], - 'tag_exists' => ["(\$tag_name is 0 ? 0 : (\$tag_name is '' ? null : array{term_id: string, term_taxonomy_id: string}|null))"], + 'tag_exists' => ["(\$term is 0 ? 0 : (\$term is '' ? null : array{term_id: string, term_taxonomy_id: string}|null))"], ]; From 46ce5f32c2edff34d0a735b2f30e2b30cedc20e8 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:19:48 +0200 Subject: [PATCH 2/7] Update wordpress-stubs.php --- wordpress-stubs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wordpress-stubs.php b/wordpress-stubs.php index c39fe150..bc5928b7 100644 --- a/wordpress-stubs.php +++ b/wordpress-stubs.php @@ -82430,7 +82430,7 @@ function wp_update_category($catarr) * @return mixed Returns null if the term does not exist. * Returns an array of the term ID and the term taxonomy ID if the pairing exists. * Returns 0 if term ID 0 is passed to the function. - * @phpstan-return ($tag_name is 0 ? 0 : ($tag_name is '' ? null : array{term_id: string, term_taxonomy_id: string}|null)) + * @phpstan-return ($term is 0 ? 0 : ($term is '' ? null : array{term_id: string, term_taxonomy_id: string}|null)) */ function tag_exists($tag_name) { From af73761b465b4ee0201e2353341aa0306743e7f1 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:24:44 +0200 Subject: [PATCH 3/7] Fix return type for tag_exists() --- functionMap62.php | 2 +- wordpress-stubs.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functionMap62.php b/functionMap62.php index 6a5d89b8..d48cb99e 100644 --- a/functionMap62.php +++ b/functionMap62.php @@ -89,5 +89,5 @@ 'get_post_permalink' => ['($post is \WP_Post ? string : string|false)'], 'term_exists' => ["(\$term is 0 ? 0 : (\$term is '' ? null : (\$taxonomy is '' ? string|null : array{term_id: string, term_taxonomy_id: string}|null)))"], 'is_term' => ["(\$term is 0 ? 0 : (\$term is '' ? null : (\$taxonomy is '' ? string|null : array{term_id: string, term_taxonomy_id: string}|null)))"], - 'tag_exists' => ["(\$term is 0 ? 0 : (\$term is '' ? null : array{term_id: string, term_taxonomy_id: string}|null))"], + 'tag_exists' => ["(\$tag_name is 0 ? 0 : (\$tag_name is '' ? null : array{term_id: string, term_taxonomy_id: string}|null))"], ]; diff --git a/wordpress-stubs.php b/wordpress-stubs.php index bc5928b7..c39fe150 100644 --- a/wordpress-stubs.php +++ b/wordpress-stubs.php @@ -82430,7 +82430,7 @@ function wp_update_category($catarr) * @return mixed Returns null if the term does not exist. * Returns an array of the term ID and the term taxonomy ID if the pairing exists. * Returns 0 if term ID 0 is passed to the function. - * @phpstan-return ($term is 0 ? 0 : ($term is '' ? null : array{term_id: string, term_taxonomy_id: string}|null)) + * @phpstan-return ($tag_name is 0 ? 0 : ($tag_name is '' ? null : array{term_id: string, term_taxonomy_id: string}|null)) */ function tag_exists($tag_name) { From 4f9bba5a3da262a99436c058a22c13ef1ac06fee Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:16:35 +0200 Subject: [PATCH 4/7] Remove code duplication for 6.2 handling --- finder.php | 2 +- functionMap.php | 7 +- functionMap62.php | 93 ----- generate.sh | 36 +- visitor62.php | 892 ---------------------------------------------- 5 files changed, 18 insertions(+), 1012 deletions(-) delete mode 100644 functionMap62.php delete mode 100644 visitor62.php diff --git a/finder.php b/finder.php index f4944e26..6682291b 100644 --- a/finder.php +++ b/finder.php @@ -41,6 +41,6 @@ //->notPath('wp-includes/theme-compat/footer.php') //->notPath('wp-includes/theme-compat/header.php') //->notPath('wp-includes/theme-compat/sidebar.php') -// ->notPath('wp-includes/Requests/src') + //->notPath('wp-includes/Requests/src') ->sortByName() ; diff --git a/functionMap.php b/functionMap.php index 0c3b819e..8ec47ba6 100644 --- a/functionMap.php +++ b/functionMap.php @@ -1,6 +1,11 @@ , filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error'; +$caseInsensitiveDictionary = '\WpOrg\Requests\Utility\CaseInsensitiveDictionary'; +if (file_exists(__DIR__ . '/source/wordpress/wp-includes/Requests/Cookie/Jar.php')) { + $caseInsensitiveDictionary = '\Requests_Utility_CaseInsensitiveDictionary'; +} + +$httpReturnType = "array{headers: $caseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error"; $cronArgsType = 'list'; $wpWidgetRssFormArgsType = 'array{number: int, error: bool, title?: string, url?: string, items?: int, show_summary?: int, show_author?: int, show_date?: int}'; $wpWidgetRssFormInputType = 'array{title?: bool, url?: bool, items?: bool, show_summary?: bool, show_author?: bool, show_date?: bool}'; diff --git a/functionMap62.php b/functionMap62.php deleted file mode 100644 index d48cb99e..00000000 --- a/functionMap62.php +++ /dev/null @@ -1,93 +0,0 @@ -, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error'; -$cronArgsType = 'list'; -$wpWidgetRssFormArgsType = 'array{number: int, error: bool, title?: string, url?: string, items?: int, show_summary?: int, show_author?: int, show_date?: int}'; -$wpWidgetRssFormInputType = 'array{title?: bool, url?: bool, items?: bool, show_summary?: bool, show_author?: bool, show_date?: bool}'; - -/** - * This array is in the same format as the function map array in PHPStan: - * - * '' => ['', ''=>''] - * - * For classes: - * - * '' => [null, ''=>''] - * - * @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"'], - 'addslashes_gpc' => ['T', '@phpstan-template'=>'T', 'gpc'=>'T'], - 'get_objects_in_term' => ['string[]|WP_Error', 'args'=>'array{order?: string}'], - 'have_posts' => ['bool', '@phpstan-impure'=>''], - 'rawurlencode_deep' => ['T', '@phpstan-template'=>'T', 'value'=>'T'], - 'remove_meta_box' => ['void', 'context'=>'"normal"|"side"|"advanced"'], - 'sanitize_category' => ['T', '@phpstan-template'=>'T of array|object', 'category'=>'T'], - 'sanitize_post' => ['T', '@phpstan-template'=>'T of array|object', 'post'=>'T'], - 'sanitize_term' => ['T', '@phpstan-template'=>'T of array|object', 'term'=>'T'], - 'stripslashes_deep' => ['T', '@phpstan-template'=>'T', 'value'=>'T'], - 'urldecode_deep' => ['T', '@phpstan-template'=>'T', 'value'=>'T'], - 'urlencode_deep' => ['T', '@phpstan-template'=>'T', 'value'=>'T'], - 'wp_clear_scheduled_hook' => ['int|false|WP_Error', 'args'=>$cronArgsType], - 'wp_get_schedule' => ['string|false', 'args'=>$cronArgsType], - 'wp_get_scheduled_event' => ['object|false', 'args'=>$cronArgsType], - '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_List_Table::set_pagination_args' => ['void', 'args'=>'array{total_items?: int, total_pages?: int, per_page?: int}'], - 'wp_next_scheduled' => ['int|false', 'args'=>$cronArgsType], - 'WP_Post_Type::__construct' => ['void', 'args'=>'array'], - 'WP_Query::have_posts' => ['bool', '@phpstan-impure'=>''], - 'wp_remote_get' => [$httpReturnType], - 'wp_remote_head' => [$httpReturnType], - 'wp_remote_post' => [$httpReturnType], - 'wp_remote_request' => [$httpReturnType], - 'wp_reschedule_event' => ['bool|WP_Error', 'args'=>$cronArgsType], - 'wp_safe_remote_get' => [$httpReturnType], - 'wp_safe_remote_head' => [$httpReturnType], - 'wp_safe_remote_post' => [$httpReturnType], - 'wp_safe_remote_request' => [$httpReturnType], - 'wp_schedule_event' => ['bool|WP_Error', 'args'=>$cronArgsType], - 'wp_schedule_single_event' => ['bool|WP_Error', 'args'=>$cronArgsType], - 'wp_slash' => ['T', '@phpstan-template'=>'T', 'value'=>'T'], - 'WP_Taxonomy::__construct' => ['void', 'args'=>'array'], - 'wp_unschedule_event' => ['bool|WP_Error', 'args'=>$cronArgsType], - 'wp_unslash' => ['T', '@phpstan-template'=>'T', 'value'=>'T'], - 'wp_widget_rss_form' => ['void', 'args'=>$wpWidgetRssFormArgsType, 'input'=>$wpWidgetRssFormInputType], - 'WP_REST_Request' => [null, '@phpstan-template'=>'T of array', '@phpstan-implements'=>'ArrayAccess, value-of>'], - 'WP_REST_Request::offsetExists' => ['bool', 'offset'=>'@param key-of'], - 'WP_REST_Request::offsetGet' => ['T[TOffset]', '@phpstan-template'=>'TOffset of key-of', 'offset'=>'TOffset'], - 'WP_REST_Request::offsetSet' => ['void', '@phpstan-template'=>'TOffset of key-of', 'offset'=>'TOffset', 'value'=>'T[TOffset]'], - 'WP_REST_Request::offsetUnset' => ['void', '@phpstan-template'=>'TOffset of key-of', 'offset'=>'TOffset'], - 'WP_Theme' => [null, '@phpstan-type'=>"ThemeKey 'Name'|'Version'|'Status'|'Title'|'Author'|'Author Name'|'Author URI'|'Description'|'Template'|'Stylesheet'|'Template Files'|'Stylesheet Files'|'Template Dir'|'Stylesheet Dir'|'Screenshot'|'Tags'|'Theme Root'|'Theme Root URI'|'Parent Theme'"], - 'WP_Theme::get' => ["(\$header is 'Name'|'ThemeURI'|'Description'|'Author'|'AuthorURI'|'Version'|'Template'|'Status'|'Tags'|'TextDomain'|'DomainPath'|'RequiresWP'|'RequiresPHP'|'UpdateURI' ? (\$header is 'Tags' ? string[] : string) : false)"], - 'WP_Theme::offsetExists' => ['($offset is ThemeKey ? true : false)'], - 'WP_Theme::offsetGet' => ['($offset is ThemeKey ? mixed : null)'], - 'WP_Block_List' => [null, '@phpstan-implements'=>'ArrayAccess'], - 'WP_Block_List::offsetExists' => ['bool', 'index'=>'int'], - 'WP_Block_List::offsetGet' => ['WP_Block|null', 'index'=>'int'], - 'WP_Block_List::offsetSet' => ['void', 'index'=>'int|null'], - 'WP_Block_List::offsetUnset' => ['void', 'index'=>'int'], - 'is_wp_error' => ['bool', '@phpstan-assert-if-true'=>'\WP_Error $thing'], - 'current_time' => ["(\$type is 'timestamp'|'U' ? int : string)"], - 'mysql2date' => ["(\$format is 'G'|'U' ? int|false : string|false)"], - 'get_post_types' => ["(\$output is 'names' ? array : array)"], - 'get_taxonomies' => ["(\$output is 'names' ? array : array)"], - 'get_object_taxonomies' => ["(\$output is 'names' ? array : array)"], - 'get_comment' => ["(\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Comment|null))"], - 'get_post' => ["(\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Post|null))"], - 'get_page_by_path' => ["(\$output is 'ARRAY_A' ? array|null : (\$output is 'ARRAY_N' ? array|null : \WP_Post|null))"], - 'has_action' => ['($callback is false ? bool : false|int)'], - 'has_filter' => ['($callback is false ? bool : false|int)'], - 'get_permalink' => ['($post is \WP_Post ? string : string|false)'], - 'get_the_permalink' => ['($post is \WP_Post ? string : string|false)'], - 'get_post_permalink' => ['($post is \WP_Post ? string : string|false)'], - 'term_exists' => ["(\$term is 0 ? 0 : (\$term is '' ? null : (\$taxonomy is '' ? string|null : array{term_id: string, term_taxonomy_id: string}|null)))"], - 'is_term' => ["(\$term is 0 ? 0 : (\$term is '' ? null : (\$taxonomy is '' ? string|null : array{term_id: string, term_taxonomy_id: string}|null)))"], - 'tag_exists' => ["(\$tag_name is 0 ? 0 : (\$tag_name is '' ? null : array{term_id: string, term_taxonomy_id: string}|null))"], -]; diff --git a/generate.sh b/generate.sh index 3d6ed941..9c3b0a8a 100755 --- a/generate.sh +++ b/generate.sh @@ -14,31 +14,17 @@ if [ ! -d vendor ]; then composer update fi -if [ -r source/wordpress/wp-includes/Requests/Cookie/Jar.php ]; then - # Exclude globals. - "$(dirname "$0")/vendor/bin/generate-stubs" \ - --force \ - --finder=finder.php \ - --visitor=visitor.php \ - --header="$HEADER" \ - --functions \ - --classes \ - --interfaces \ - --traits \ - --out="$FILE" -else - # Exclude globals. - "$(dirname "$0")/vendor/bin/generate-stubs" \ - --force \ - --finder=finder.php \ - --visitor=visitor62.php \ - --header="$HEADER" \ - --functions \ - --classes \ - --interfaces \ - --traits \ - --out="$FILE" -fi +# Exclude globals. +"$(dirname "$0")/vendor/bin/generate-stubs" \ + --force \ + --finder=finder.php \ + --visitor=visitor.php \ + --header="$HEADER" \ + --functions \ + --classes \ + --interfaces \ + --traits \ + --out="$FILE" # Use literal-string type for wpdb::prepare() query statement parameter. sed -i -e 's#^.*@param string \+\$query \+Query statement.*$#&\n * @phpstan-param literal-string $query#' "$FILE" diff --git a/visitor62.php b/visitor62.php deleted file mode 100644 index d575727e..00000000 --- a/visitor62.php +++ /dev/null @@ -1,892 +0,0 @@ -children as $child) { - if ($child->name === null) { - return false; - } - } - - return true; - } - - public function isMixedShape(): bool - { - $hasStaticKey = false; - - foreach ($this->children as $child) { - if ($child->name !== null) { - $hasStaticKey = true; - } elseif ($hasStaticKey) { - return true; - } - } - - return false; - } -} - -final class WordPressTag extends WithChildren -{ - /** - * @var string - */ - public $tag; - - /** - * @var string - */ - public $type; - - /** - * @var ?string - */ - public $name = null; - - /** - * @var ?string - */ - public $description = null; - - /** - * @return string[] - */ - public function format(): array - { - if ($this->isMixedShape()) { - return []; - } - - $strings = []; - $childStrings = []; - $level = 1; - - if (! $this->isArrayShape()) { - $level = 0; - } - - foreach ($this->children as $child) { - $childStrings = array_merge($childStrings, $child->format($level)); - } - - if (count($childStrings) === 0) { - return []; - } - - $name = ($this->name !== null) ? (' $' . $this->name) : ''; - - if ($this->isArrayShape()) { - $strings[] = sprintf( - '%s %s{', - $this->tag, - $this->type - ); - } else { - if (count($this->children) > 0 && count($this->children[0]->children) > 0) { - $strings[] = sprintf( - '%s %stag, - $this->type - ); - } else { - $strings[] = sprintf( - '%s array%s', - $this->tag, - $this->type, - $name - ); - - return $strings; - } - } - - $strings = array_merge($strings, $childStrings); - $description = ''; - - if ($this->description !== null) { - $description = ' ' . $this->description; - } - - if ($this->isArrayShape()) { - $strings[] = sprintf( - '}%s%s', - $name, - $description - ); - } else { - $strings[] = sprintf( - '}>%s%s', - $name, - $description - ); - } - - return $strings; - } -} - -final class WordPressArg extends WithChildren -{ - /** - * @var string - */ - public $type; - - /** - * @var bool - */ - public $optional = false; - - /** - * @var ?string - */ - public $name = null; - - /** - * @return string[] - */ - public function format(int $level = 1): array - { - $strings = []; - $padding = str_repeat(' ', ($level * 2)); - - if ($this->isMixedShape()) { - return []; - } - - if (count($this->children) > 0) { - $childStrings = []; - - foreach ($this->children as $child) { - $childStrings = array_merge($childStrings, $child->format($level + 1)); - } - - if (count($childStrings) === 0) { - return []; - } - - if ($this->isArrayShape()) { - if ($this->name !== null) { - $strings[] = sprintf( - '%s%s%s: %s{', - $padding, - $this->name, - ($this->optional) ? '?' : '', - $this->type - ); - } - } else { - $strings[] = sprintf( - '%s%s%s: arrayname, - ($this->optional) ? '?' : '', - $this->type - ); - } - - $strings = array_merge($strings, $childStrings); - - if ($this->isArrayShape()) { - if ($this->name !== null) { - $strings[] = $padding . '},'; - } - } else { - $strings[] = $padding . '}>,'; - } - } else { - $strings[] = sprintf( - '%s%s%s: %s,', - $padding, - $this->name, - ($this->optional) ? '?' : '', - $this->type - ); - } - - return $strings; - } -} - -return new class extends NodeVisitor { - - /** - * @var \phpDocumentor\Reflection\DocBlockFactory - */ - private $docBlockFactory; - - /** - * @var ?array> - */ - private $functionMap = null; - - /** - * @var array> - */ - private $additionalTags = []; - - /** - * @var array> - */ - private $additionalTagStrings = []; - - public function __construct() - { - $this->docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance(); - } - - public function enterNode(Node $node) - { - parent::enterNode($node); - - if (!($node instanceof Function_) && !($node instanceof ClassMethod) && !($node instanceof Property) && !($node instanceof Class_)) { - return null; - } - - $docComment = $node->getDocComment(); - - if (!($docComment instanceof Doc)) { - return null; - } - - $symbolName = self::getNodeName($node); - - if ($node instanceof ClassMethod) { - /** @var \PhpParser\Node\Stmt\Class_ $parent */ - $parent = $this->stack[count($this->stack) - 2]; - - if (isset($parent->name)) { - $symbolName = sprintf( - '%1$s::%2$s', - $parent->name->name, - $node->name->name - ); - } - } - - $additions = $this->generateAdditionalTagsFromDoc($docComment); - $node->setAttribute('fullSymbolName', $symbolName); - - if (count($additions) > 0) { - $this->additionalTags[ $symbolName ] = $additions; - } - - $additions = $this->getAdditionalTagsFromMap($symbolName); - - if (count($additions) > 0) { - $this->additionalTagStrings[ $symbolName ] = $additions; - } - - return null; - } - - private static function getNodeName(Node $node): string - { - if ((($node instanceof Function_) || ($node instanceof ClassMethod) || ($node instanceof Class_)) && $node->name instanceof Identifier) { - return $node->name->name; - } - - if ($node instanceof Property) { - return sprintf( - 'property_%s', - uniqid() - ); - } - - return ''; - } - - /** - * @return Node[] - */ - public function getStubStmts(): array - { - $stmts = parent::getStubStmts(); - - foreach ($stmts as $stmt) { - $this->postProcessNode($stmt); - } - - return $stmts; - } - - private function postProcessNode(Node $node): void - { - if (isset($node->stmts) && is_array($node->stmts)) { - foreach ($node->stmts as $stmt) { - $this->postProcessNode($stmt); - } - } - - if (! ($node instanceof Function_) && ! ($node instanceof ClassMethod) && ! ($node instanceof Property) && ! ($node instanceof Class_)) { - return; - } - - $name = $node->getAttribute('fullSymbolName'); - - if ($name === null) { - return; - } - - $docComment = $node->getDocComment(); - - if (!($docComment instanceof Doc)) { - return; - } - - $newDocComment = $this->addTags($name, $docComment); - - if ($newDocComment !== null) { - $node->setDocComment($newDocComment); - } - - if (! isset($this->additionalTagStrings[ $name ])) { - return; - } - - $docComment = $node->getDocComment(); - - if (!($docComment instanceof Doc)) { - return; - } - - $newDocComment = $this->addStringTags($name, $docComment); - - if ($newDocComment !== null) { - $node->setDocComment($newDocComment); - } - } - - /** - * @return array - */ - private function generateAdditionalTagsFromDoc(Doc $docComment): array - { - $docCommentText = $docComment->getText(); - - try { - $docblock = $this->docBlockFactory->create($docCommentText); - } catch ( \RuntimeException $e ) { - return []; - } catch ( \InvalidArgumentException $e ) { - return []; - } - - /** @var \phpDocumentor\Reflection\DocBlock\Tags\Param[] */ - $params = $docblock->getTagsByName('param'); - - /** @var \phpDocumentor\Reflection\DocBlock\Tags\Return_[] */ - $returns = $docblock->getTagsByName('return'); - - /** @var \phpDocumentor\Reflection\DocBlock\Tags\Var_[] */ - $vars = $docblock->getTagsByName('var'); - - /** @var WordPressTag[] $additions */ - $additions = []; - - foreach ($params as $param) { - if (! $param instanceof Param) { - continue; - } - - $addition = self::getAdditionFromParam($param); - - if ($addition !== null) { - $additions[] = $addition; - } - } - - foreach ($returns as $return) { - if (! $return instanceof Return_) { - continue; - } - - $addition = self::getAdditionFromReturn($return); - - if ($addition !== null) { - $additions[] = $addition; - } - } - - foreach ($vars as $var) { - if (! $var instanceof Var_) { - continue; - } - - $addition = self::getAdditionFromVar($var); - - if ($addition !== null) { - $additions[] = $addition; - } - } - - return $additions; - } - - private function addTags(string $name, Doc $docComment): ?Doc - { - if (isset($this->additionalTags[ $name ])) { - $additions = $this->additionalTags[ $name ]; - } else { - $additions = []; - } - - $docCommentText = $docComment->getText(); - - try { - $docblock = $this->docBlockFactory->create($docCommentText); - } catch ( \RuntimeException $e ) { - return null; - } catch ( \InvalidArgumentException $e ) { - return null; - } - - $additions = $this->discoverInheritedArgs($docblock, $additions); - - /** @var string[] $additionStrings */ - $additionStrings = array_map( function(WordPressTag $tag): string { - $lines = $tag->format(); - - if (count($lines) === 0) { - return ''; - } - - return " * " . implode("\n * ", $lines); - }, $additions); - - $additionStrings = array_filter($additionStrings); - - if (count($additionStrings) === 0) { - return null; - } - - $newDocComment = sprintf( - "%s\n%s\n */", - substr($docCommentText, 0, -4), - implode("\n", $additionStrings) - ); - - return new Doc($newDocComment, $docComment->getStartLine(), $docComment->getStartFilePos()); - } - - /** - * @param array $additions - * @return array - */ - private function discoverInheritedArgs(DocBlock $docblock, array $additions): array - { - /** @var Param[] $params */ - $params = $docblock->getTagsByName('param'); - - $phpStanParams = array_filter($additions, function(WordPressTag $addition): bool { - return $addition->tag === '@phpstan-param'; - }); - - foreach ($params as $param) { - $inherited = $this->getInheritedTagsForParam($param); - - if (count($inherited) === 0) { - continue; - } - - foreach ($phpStanParams as $addition) { - foreach ($inherited as $inherit) { - if ($addition->name !== $inherit->name) { - continue; - } - - $addition->children = array_merge($addition->children, $inherit->children); - continue 3; - } - } - - $additions = array_merge($additions, $inherited); - } - - return $additions; - } - - /** - * @return array - */ - private function getInheritedTagsForParam(Param $param): array - { - $type = $param->getType(); - - if ($type === null) { - return []; - } - - $typeName = self::getTypeNameFromType($type); - - if ($typeName === null) { - return []; - } - - $paramDescription = $param->getDescription(); - - if ($paramDescription === null) { - return []; - } - - list($description) = explode("\n\n", $paramDescription->__toString()); - - if (strpos($description, '()') === false) { - return []; - } - - $description = str_replace("\n", ' ', $description); - $additions = []; - - foreach ($this->additionalTags as $symbolName => $tags) { - $search = sprintf( - 'see %s()', - $symbolName - ); - - if (stripos($description, $search) === false) { - continue; - } - - $match = self::getMatchingInheritedTag($param, $tags, $symbolName); - - if ($match !== null) { - $additions[] = $match; - } - } - - return $additions; - } - - /** - * @param array $tags - */ - private static function getMatchingInheritedTag(Param $param, array $tags, string $symbolName): ?WordPressTag - { - $paramName = $param->getVariableName(); - $matchNames = [ - $paramName, - 'args', - 'options', - 'query', - ]; - $matchingTags = array_filter($tags, static function(WordPressTag $tag) use ($matchNames): bool { - return in_array($tag->name, $matchNames, true); - }); - - foreach ($matchingTags as $tag) { - $addTag = clone $tag; - $addTag->name = $paramName; - $addTag->description = sprintf( - 'See %s()', - $symbolName - ); - - return $addTag; - } - - return null; - } - - /** - * @return string[] - */ - private function getAdditionalTagsFromMap(string $symbolName): array - { - if (! isset($this->functionMap)) { - $this->functionMap = require __DIR__ . '/functionMap62.php'; - } - - if (! isset($this->functionMap[$symbolName])) { - return []; - } - - $parameters = $this->functionMap[$symbolName]; - $returnType = array_shift($parameters); - $additions = []; - - foreach ($parameters as $paramName => $paramType) { - if (strpos($paramName, '@') === 0) { - $format = ( $paramType === '' ) ? '%s' : '%s %s'; - $additions[] = sprintf( - $format, - $paramName, - $paramType - ); - continue; - } - - $additions[] = sprintf( - '@phpstan-param %s $%s', - $paramType, - $paramName - ); - } - - if ($returnType) { - $additions[] = sprintf( - '@phpstan-return %s', - $returnType - ); - } - - return $additions; - } - - private function addStringTags(string $name, Doc $docComment): ?Doc - { - if ( !isset($this->additionalTagStrings[ $name ])) { - return null; - } - - $additions = $this->additionalTagStrings[ $name ]; - - $docCommentText = $docComment->getText(); - $newDocComment = sprintf( - "%s\n * %s\n */", - substr($docCommentText, 0, -4), - implode("\n * ", $additions) - ); - - return new Doc($newDocComment, $docComment->getStartLine(), $docComment->getStartFilePos()); - } - - private function getAdditionFromParam(Param $tag): ?WordPressTag - { - $tagDescription = $tag->getDescription(); - $tagVariableName = $tag->getVariableName(); - $tagVariableType = $tag->getType(); - - // Skip if information we need is missing. - if (!$tagDescription || !$tagVariableName || !$tagVariableType) { - return null; - } - - $elements = self::getElementsFromDescription($tagDescription, true); - - if (count($elements) === 0) { - return null; - } - - $tagVariableType = self::getTypeNameFromType($tagVariableType); - - if ($tagVariableType === null) { - return null; - } - - // It's common for an args parameter to accept a query var string or array with `string|array`. - // Remove the accepted string type for these so we get the strongest typing we can manage. - $tagVariableType = str_replace(['|string', 'string|'], '', $tagVariableType); - - $tag = new WordPressTag(); - $tag->tag = '@phpstan-param'; - $tag->type = $tagVariableType; - $tag->name = $tagVariableName; - $tag->children = $elements; - - return $tag; - } - - private function getAdditionFromReturn(Return_ $tag): ?WordPressTag - { - $tagDescription = $tag->getDescription(); - $tagVariableType = $tag->getType(); - - // Skip if information we need is missing. - if (!$tagDescription || !$tagVariableType) { - return null; - } - - $elements = self::getElementsFromDescription($tagDescription, false); - - if (count($elements) === 0) { - return null; - } - - $tagVariableType = self::getTypeNameFromType($tagVariableType); - - if ($tagVariableType === null) { - return null; - } - - $tag = new WordPressTag(); - $tag->tag = '@phpstan-return'; - $tag->type = $tagVariableType; - $tag->children = $elements; - - return $tag; - } - - private static function getAdditionFromVar(Var_ $tag): ?WordPressTag - { - $tagDescription = $tag->getDescription(); - $tagVariableType = $tag->getType(); - - // Skip if information we need is missing. - if (!$tagDescription || !$tagVariableType) { - return null; - } - - $elements = self::getElementsFromDescription($tagDescription, false); - - if (count($elements) === 0) { - return null; - } - - $tagVariableType = self::getTypeNameFromType($tagVariableType); - - if ($tagVariableType === null) { - return null; - } - - $tag = new WordPressTag(); - $tag->tag = '@phpstan-var'; - $tag->type = $tagVariableType; - $tag->children = $elements; - - return $tag; - } - - private static function getTypeNameFromType(Type $tagVariableType): ?string - { - return self::getTypeNameFromString($tagVariableType->__toString()); - } - - private static function getTypeNameFromString(string $tagVariable): ?string - { - // PHPStan doesn't support typed array shapes (`int[]{...}`) so replace - // typed arrays such as `int[]` with `array`. - $tagVariableType = preg_replace('#[a-zA-Z0-9_]+\[\]#', 'array', $tagVariable); - - if ($tagVariableType === null) { - return null; - } - - if (strpos($tagVariableType, 'array') === false) { - // Skip if we have hash notation that's not for an array (ie. for `object`). - return null; - } - - if (strpos($tagVariableType, 'array|') !== false) { - // Move `array` to the end of union types so the appended array shape works. - $tagVariableType = str_replace('array|', '', $tagVariableType) . '|array'; - } - - return $tagVariableType; - } - - /** - * @return WordPressArg[] - */ - private static function getElementsFromDescription(Description $tagDescription, bool $optional): array - { - $text = $tagDescription->__toString(); - - // Skip if the description doesn't contain at least one correctly - // formatted `@type`, which indicates an array hash. - if (strpos($text, ' @type ') === false) { - return []; - } - - return self::getTypesAtLevel($text, $optional, 1); - } - - /** - * @return WordPressArg[] - */ - private static function getTypesAtLevel(string $text, bool $optional, int $level): array - { - // Populate `$types` with the value of each top level `@type`. - $spaces = str_repeat(' ', ($level * 4)); - $types = preg_split("/\R+{$spaces}@type /", $text); - - if ($types === false) { - return []; - } - - unset($types[0]); - $elements = []; - - foreach ($types as $typeTag) { - $parts = preg_split('#\s+#', trim($typeTag), 3); - - if ($parts === false || count($parts) < 2) { - return []; - } - - list($type, $name) = $parts; - - $optionalArg = $optional; - $nameTrimmed = ltrim($name, '$'); - - if (is_numeric($nameTrimmed)) { - $optionalArg = false; - } elseif ($optional && ($level > 1)) { - $optionalArg = isset($parts[2]) && self::isOptional($parts[2]); - } - - if (strpos($name, '...$') !== false) { - $name = null; - } elseif (strpos($name, '$') !== 0) { - return []; - } else { - $name = $nameTrimmed; - } - - $arg = new WordPressArg(); - $arg->type = $type; - $arg->optional = $optionalArg; - $arg->name = $name; - - $nextLevel = $level + 1; - $subTypes = self::getTypesAtLevel($typeTag, $optional, $nextLevel); - - if (count($subTypes) > 0) { - $type = self::getTypeNameFromString($type); - - if ($type !== null) { - $arg->type = $type; - } - $arg->children = $subTypes; - } - - $elements[] = $arg; - } - - return $elements; - } - - private static function isOptional(string $description): bool - { - return (stripos($description, 'Optional') !== false) - || (stripos($description, 'Default ') !== false) - || (stripos($description, 'Default: ') !== false) - || (stripos($description, 'Defaults to ') !== false) - ; - } -}; From 990afcbdeab84adbafe468900802d68a2747022e Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:10:46 +0200 Subject: [PATCH 5/7] Update functionMap.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Viktor Szépe --- functionMap.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/functionMap.php b/functionMap.php index 8ec47ba6..2e697cd6 100644 --- a/functionMap.php +++ b/functionMap.php @@ -1,11 +1,10 @@ , filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error'; + if (file_exists(__DIR__ . '/source/wordpress/wp-includes/Requests/Cookie/Jar.php')) { - $caseInsensitiveDictionary = '\Requests_Utility_CaseInsensitiveDictionary'; + $httpReturnType = 'array{headers: \Requests_Utility_CaseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error'; } - -$httpReturnType = "array{headers: $caseInsensitiveDictionary, body: string, response: array{code: int,message: string}, cookies: array, filename: string|null, http_response: \WP_HTTP_Requests_Response}|\WP_Error"; $cronArgsType = 'list'; $wpWidgetRssFormArgsType = 'array{number: int, error: bool, title?: string, url?: string, items?: int, show_summary?: int, show_author?: int, show_date?: int}'; $wpWidgetRssFormInputType = 'array{title?: bool, url?: bool, items?: bool, show_summary?: bool, show_author?: bool, show_date?: bool}'; From 74415c2ea43750e5d5d48baaa48cc90f79f2e589 Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:11:08 +0200 Subject: [PATCH 6/7] Update finder.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Viktor Szépe --- finder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/finder.php b/finder.php index 6682291b..37808ed1 100644 --- a/finder.php +++ b/finder.php @@ -41,6 +41,5 @@ //->notPath('wp-includes/theme-compat/footer.php') //->notPath('wp-includes/theme-compat/header.php') //->notPath('wp-includes/theme-compat/sidebar.php') - //->notPath('wp-includes/Requests/src') ->sortByName() ; From 47b4584c8ff9ab9b7bee6059bc6be0d48e0a679f Mon Sep 17 00:00:00 2001 From: IanDelMar <42134098+IanDelMar@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:17:12 +0200 Subject: [PATCH 7/7] Re-add notPath('wp-includes/Requests/src') --- finder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/finder.php b/finder.php index 37808ed1..a2c5687c 100644 --- a/finder.php +++ b/finder.php @@ -41,5 +41,6 @@ //->notPath('wp-includes/theme-compat/footer.php') //->notPath('wp-includes/theme-compat/header.php') //->notPath('wp-includes/theme-compat/sidebar.php') +// ->notPath('wp-includes/Requests/src') ->sortByName() ;