From 5fc8a03bbcc7958f1deb29b526f0dc6329f0358a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 1 Oct 2026 08:53:28 +0000 Subject: [PATCH 1/2] Docs: Add PHPStan array shapes. Adds array shapes and refined array types to functions that return or accept arrays with a known set of keys. The HTTP response is defined once, as the `HTTP_Response` type on `WP_Http`, and repeated on the `wp_remote_*()` and `wp_safe_remote_*()` functions. It includes the response of a non-blocking request, which has no `filename` and `false` for the code and message. Compared to the function map in php-stubs/wordpress-stubs, `WP_Widget::widget()` requires only the four keys `the_widget()` passes, `wp_widget_rss_form()` accepts the `'__i__'` placeholder as the widget number, the script tag attributes accept `false`, and `get_shortcode_tags_in_content()` returns `list`. The map's `list` for cron arguments, its shapes for `WP_List_Table::set_pagination_args()` and `Custom_Image_Header::set_header_image()`, and its conditional return for `get_block_wrapper_attributes()` are left out, since core does not match them. See #65817. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VieGtSQtUoVfHiGwTmkvtc --- src/wp-admin/includes/bookmark.php | 4 ++ src/wp-admin/includes/list-table.php | 1 + src/wp-admin/includes/media.php | 4 ++ src/wp-admin/includes/ms.php | 2 + src/wp-admin/includes/nav-menu.php | 10 +++ src/wp-admin/includes/post.php | 2 + src/wp-includes/class-wp-block-supports.php | 2 + src/wp-includes/class-wp-dependencies.php | 2 + src/wp-includes/class-wp-http.php | 17 +++++ src/wp-includes/class-wp-widget.php | 16 +++++ src/wp-includes/feed.php | 2 + src/wp-includes/formatting.php | 2 + src/wp-includes/functions.php | 1 + src/wp-includes/http.php | 72 +++++++++++++++++++++ src/wp-includes/nav-menu.php | 2 + src/wp-includes/robots-template.php | 18 ++++++ src/wp-includes/script-loader.php | 4 ++ src/wp-includes/shortcodes.php | 2 + src/wp-includes/widgets.php | 30 +++++++++ 19 files changed, 193 insertions(+) diff --git a/src/wp-admin/includes/bookmark.php b/src/wp-admin/includes/bookmark.php index aae87b9abc60f..43acb61be4210 100644 --- a/src/wp-admin/includes/bookmark.php +++ b/src/wp-admin/includes/bookmark.php @@ -56,6 +56,8 @@ function edit_link( $link_id = 0 ) { * @since 2.0.0 * * @return stdClass Default link object. + * + * @phpstan-return object{link_url: string, link_name: string, link_visible: 'Y'}&stdClass */ function get_default_link_to_edit() { $link = new stdClass(); @@ -122,6 +124,8 @@ function wp_delete_link( $link_id ) { * * @param int $link_id Link ID to look up. * @return int[]|WP_Error The IDs of the requested link's categories, or else a WP_Error if the `link_category` taxonomy was unregistered. + * + * @phpstan-return array>|WP_Error */ function wp_get_link_cats( $link_id = 0 ) { $cats = wp_get_object_terms( $link_id, 'link_category', array( 'fields' => 'ids' ) ); diff --git a/src/wp-admin/includes/list-table.php b/src/wp-admin/includes/list-table.php index 565144768e7bb..442fea665fd6f 100644 --- a/src/wp-admin/includes/list-table.php +++ b/src/wp-admin/includes/list-table.php @@ -36,6 +36,7 @@ * * @phpstan-template T of WP_List_Table * @phpstan-param class-string $class_name + * @phpstan-param array{screen?: string|WP_Screen|null} $args * @phpstan-return T|false */ function _get_list_table( $class_name, $args = array() ) { diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index 6134db45b6a95..bf9dcf9f5d165 100644 --- a/src/wp-admin/includes/media.php +++ b/src/wp-admin/includes/media.php @@ -1237,6 +1237,8 @@ function image_align_input_fields( $post, $checked = '' ) { * @param WP_Post $post * @param bool|string $check * @return array An array of data for the image size input fields. + * + * @phpstan-return array{label: string, input: 'html', html: string} */ function image_size_input_fields( $post, $check = '' ) { /** @@ -1931,6 +1933,8 @@ function get_media_item( $attachment_id, $args = null ) { * @param int $attachment_id * @param array $args * @return array An array containing the media item and its metadata. + * + * @phpstan-return array{item: string, meta: string} */ function get_compat_media_markup( $attachment_id, $args = null ) { $post = get_post( $attachment_id ); diff --git a/src/wp-admin/includes/ms.php b/src/wp-admin/includes/ms.php index 155170aaf4f41..da2889b74c5bb 100644 --- a/src/wp-admin/includes/ms.php +++ b/src/wp-admin/includes/ms.php @@ -1187,6 +1187,8 @@ function network_edit_site_nav( $args = array() ) { * @since 4.9.0 * * @return array Help tab arguments. + * + * @phpstan-return array{id: 'overview', title: string, content: non-falsy-string} */ function get_site_screen_help_tab_args() { return array( diff --git a/src/wp-admin/includes/nav-menu.php b/src/wp-admin/includes/nav-menu.php index f26d63d528e78..7eb8763b86cbe 100644 --- a/src/wp-admin/includes/nav-menu.php +++ b/src/wp-admin/includes/nav-menu.php @@ -1348,6 +1348,16 @@ function wp_get_nav_menu_to_edit( $menu_id = 0 ) { * @since 3.0.0 * * @return string[] Array of column titles keyed by their column name. + * + * @phpstan-return array{ + * _title: string, + * cb: '', + * link-target: string, + * title-attribute: string, + * css-classes: string, + * xfn: string, + * description: string, + * } */ function wp_nav_menu_manage_columns() { return array( diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php index c39135316978f..e33659f37bc5e 100644 --- a/src/wp-admin/includes/post.php +++ b/src/wp-admin/includes/post.php @@ -1220,6 +1220,8 @@ function _fix_attachment_links( $post ) { * * @param string $type The post_type you want the statuses for. Default 'post'. * @return string[] An array of all the statuses for the supplied post type. + * + * @phpstan-return list */ function get_available_post_statuses( $type = 'post' ) { $statuses = wp_count_posts( $type ); diff --git a/src/wp-includes/class-wp-block-supports.php b/src/wp-includes/class-wp-block-supports.php index cf2d84f3b6756..6c93acc0b49bd 100644 --- a/src/wp-includes/class-wp-block-supports.php +++ b/src/wp-includes/class-wp-block-supports.php @@ -197,6 +197,8 @@ private function register_attributes() { * * @param string[] $extra_attributes Optional. Array of extra attributes to render on the block wrapper. * @return string String of HTML attributes. + * + * @phpstan-param array $extra_attributes */ function get_block_wrapper_attributes( $extra_attributes = array() ) { $new_attributes = WP_Block_Supports::get_instance()->apply_block_supports(); diff --git a/src/wp-includes/class-wp-dependencies.php b/src/wp-includes/class-wp-dependencies.php index 80559201dda03..7ad9376948142 100644 --- a/src/wp-includes/class-wp-dependencies.php +++ b/src/wp-includes/class-wp-dependencies.php @@ -74,6 +74,8 @@ class WP_Dependencies { * @since 2.8.0 * * @var (int|false)[] + * + * @phpstan-var array */ public $groups = array(); diff --git a/src/wp-includes/class-wp-http.php b/src/wp-includes/class-wp-http.php index 13b82d95bfbd2..6283ce6fc1c83 100644 --- a/src/wp-includes/class-wp-http.php +++ b/src/wp-includes/class-wp-http.php @@ -29,6 +29,15 @@ * Debugging includes several actions, which pass different variables for debugging the HTTP API. * * @since 2.7.0 + * + * @phpstan-type HTTP_Response array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * } */ #[AllowDynamicProperties] class WP_Http { @@ -166,6 +175,8 @@ class WP_Http { * @type string|null $filename Optional. Filename of the response. * @type WP_HTTP_Requests_Response|null $http_response Response object. * } + * + * @phpstan-return HTTP_Response|WP_Error */ public function request( $url, $args = array() ) { $defaults = array( @@ -633,6 +644,8 @@ 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. See WP_Http::response() for details. + * + * @phpstan-return HTTP_Response|WP_Error */ public function post( $url, $args = array() ) { $defaults = array( 'method' => 'POST' ); @@ -651,6 +664,8 @@ 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. See WP_Http::response() for details. + * + * @phpstan-return HTTP_Response|WP_Error */ public function get( $url, $args = array() ) { $defaults = array( 'method' => 'GET' ); @@ -669,6 +684,8 @@ 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. See WP_Http::response() for details. + * + * @phpstan-return HTTP_Response|WP_Error */ public function head( $url, $args = array() ) { $defaults = array( 'method' => 'HEAD' ); diff --git a/src/wp-includes/class-wp-widget.php b/src/wp-includes/class-wp-widget.php index b131c50db3226..cd844cea7314f 100644 --- a/src/wp-includes/class-wp-widget.php +++ b/src/wp-includes/class-wp-widget.php @@ -109,6 +109,22 @@ class WP_Widget { * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance The settings for the particular instance of the widget. + * + * @phpstan-param array{ + * name?: string, + * id?: string, + * description?: string, + * class?: string, + * before_widget: string, + * after_widget: string, + * before_title: string, + * after_title: string, + * before_sidebar?: string, + * after_sidebar?: string, + * show_in_rest?: bool, + * widget_id?: string, + * widget_name?: string, + * } $args */ public function widget( $args, $instance ) { die( 'function WP_Widget::widget() must be overridden in a subclass.' ); diff --git a/src/wp-includes/feed.php b/src/wp-includes/feed.php index 7f9988b29f246..d820a25fda449 100644 --- a/src/wp-includes/feed.php +++ b/src/wp-includes/feed.php @@ -595,6 +595,8 @@ function atom_enclosure() { * * @param string $data Input string. * @return array array(type, value) + * + * @phpstan-return array{'html'|'text'|'xhtml', string} */ function prep_atom_text_construct( $data ) { if ( ! str_contains( $data, '<' ) && ! str_contains( $data, '&' ) ) { diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index c39beaf7b1d68..da6ec1aec8e85 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -611,6 +611,8 @@ function wpautop( $text, $br = true ) { * * @param string $input The text which has to be formatted. * @return string[] Array of the formatted text. + * + * @phpstan-return non-empty-list */ function wp_html_split( $input ) { return preg_split( get_html_split_regex(), $input, -1, PREG_SPLIT_DELIM_CAPTURE ); diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 393f50a3aad58..46b4f4763b4db 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -8308,6 +8308,7 @@ function wp_unique_prefixed_id( $prefix = '' ) { * @param string $prefix Optional. A prefix to prepend to the generated ID. Default empty string. * @return string The generated unique ID for the array. * + * @phpstan-param non-empty-array $data * @phpstan-return ( $prefix is lowercase-string ? lowercase-string&non-falsy-string : non-falsy-string ) */ function wp_unique_id_from_values( array $data, string $prefix = '' ): string { diff --git a/src/wp-includes/http.php b/src/wp-includes/http.php index 1a189a261306b..f6ca6022a6728 100644 --- a/src/wp-includes/http.php +++ b/src/wp-includes/http.php @@ -48,6 +48,15 @@ function _wp_http_get_object() { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response or WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_safe_remote_request( $url, $args = array() ) { $args['reject_unsafe_urls'] = true; @@ -77,6 +86,15 @@ function wp_safe_remote_request( $url, $args = array() ) { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response or WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_safe_remote_get( $url, $args = array() ) { $args['reject_unsafe_urls'] = true; @@ -106,6 +124,15 @@ function wp_safe_remote_get( $url, $args = array() ) { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response or WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_safe_remote_post( $url, $args = array() ) { $args['reject_unsafe_urls'] = true; @@ -135,6 +162,15 @@ function wp_safe_remote_post( $url, $args = array() ) { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response or WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_safe_remote_head( $url, $args = array() ) { $args['reject_unsafe_urls'] = true; @@ -162,6 +198,15 @@ function wp_safe_remote_head( $url, $args = array() ) { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response array or a WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_remote_request( $url, $args = array() ) { $http = _wp_http_get_object(); @@ -183,6 +228,15 @@ function wp_remote_request( $url, $args = array() ) { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response or WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_remote_get( $url, $args = array() ) { $http = _wp_http_get_object(); @@ -204,6 +258,15 @@ function wp_remote_get( $url, $args = array() ) { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response or WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_remote_post( $url, $args = array() ) { $http = _wp_http_get_object(); @@ -225,6 +288,15 @@ function wp_remote_post( $url, $args = array() ) { * See WP_Http::request() for information on accepted arguments. * @return array|WP_Error The response or WP_Error on failure. * See WP_Http::request() for information on return value. + * + * @phpstan-return array{ + * headers: \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array, + * body: string, + * response: array{code: int|false, message: string|false}, + * cookies: WP_Http_Cookie[], + * filename?: string|null, + * http_response: WP_HTTP_Requests_Response|null, + * }|WP_Error */ function wp_remote_head( $url, $args = array() ) { $http = _wp_http_get_object(); diff --git a/src/wp-includes/nav-menu.php b/src/wp-includes/nav-menu.php index ed49892ac0eb6..269a26b5c50d5 100644 --- a/src/wp-includes/nav-menu.php +++ b/src/wp-includes/nav-menu.php @@ -86,6 +86,8 @@ function is_nav_menu( $menu ) { * @global array $_wp_registered_nav_menus * * @param string[] $locations Associative array of menu location identifiers (like a slug) and descriptive text. + * + * @phpstan-param array $locations */ function register_nav_menus( $locations = array() ) { global $_wp_registered_nav_menus; diff --git a/src/wp-includes/robots-template.php b/src/wp-includes/robots-template.php index e719e745d61e7..14657f74340b0 100644 --- a/src/wp-includes/robots-template.php +++ b/src/wp-includes/robots-template.php @@ -66,6 +66,9 @@ function wp_robots() { * * @param array $robots Associative array of robots directives. * @return array Filtered robots directives. + * + * @phpstan-param array $robots + * @phpstan-return array */ function wp_robots_noindex( array $robots ) { if ( ! get_option( 'blog_public' ) ) { @@ -88,6 +91,9 @@ function wp_robots_noindex( array $robots ) { * * @param array $robots Associative array of robots directives. * @return array Filtered robots directives. + * + * @phpstan-param array $robots + * @phpstan-return array */ function wp_robots_noindex_embeds( array $robots ) { if ( is_embed() ) { @@ -114,6 +120,9 @@ function wp_robots_noindex_embeds( array $robots ) { * * @param array $robots Associative array of robots directives. * @return array Filtered robots directives. + * + * @phpstan-param array $robots + * @phpstan-return array */ function wp_robots_noindex_search( array $robots ) { if ( is_search() ) { @@ -136,6 +145,9 @@ function wp_robots_noindex_search( array $robots ) { * * @param array $robots Associative array of robots directives. * @return array Filtered robots directives. + * + * @phpstan-param array $robots + * @phpstan-return array */ function wp_robots_no_robots( array $robots ) { $robots['noindex'] = true; @@ -163,6 +175,9 @@ function wp_robots_no_robots( array $robots ) { * * @param array $robots Associative array of robots directives. * @return array Filtered robots directives. + * + * @phpstan-param array $robots + * @phpstan-return array */ function wp_robots_sensitive_page( array $robots ) { $robots['noindex'] = true; @@ -184,6 +199,9 @@ function wp_robots_sensitive_page( array $robots ) { * * @param array $robots Associative array of robots directives. * @return array Filtered robots directives. + * + * @phpstan-param array $robots + * @phpstan-return array */ function wp_robots_max_image_preview_large( array $robots ) { if ( get_option( 'blog_public' ) ) { diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 6b01efa1718ce..9d4f9a03a9c8e 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2939,6 +2939,8 @@ function wp_enqueue_editor_format_library_assets() { * * @param array $attributes Key-value pairs representing `