From 7fa3597f8aeeca3d9cad7b542e84f2657326de24 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 1 Oct 2026 08:53:28 +0000 Subject: [PATCH 01/19] Docs: Add refined string types to PHPStan return and parameter types. Adds `non-empty-string`, `non-falsy-string` and `lowercase-string` return types where the function always produces such a string, and narrows a few parameters whose function rejects an empty value: `wp_register_ability()`, `register_post_type()`, `add_shortcode()` and `wp_upload_bits()`. The map's `non-empty-string` on the `$hook_name` parameter of the hook functions and `non-falsy-string` on `register_rest_route()` are left out, since core itself passes plain strings to them. See #65817. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VieGtSQtUoVfHiGwTmkvtc --- .../includes/class-wp-comments-list-table.php | 2 ++ src/wp-admin/includes/media.php | 8 ++++++++ src/wp-admin/includes/ms.php | 2 ++ src/wp-admin/includes/plugin.php | 2 ++ src/wp-admin/includes/template.php | 2 ++ src/wp-admin/includes/widgets.php | 2 ++ src/wp-includes/abilities-api.php | 2 ++ src/wp-includes/block-supports/elements.php | 2 ++ src/wp-includes/block-template-utils.php | 2 ++ src/wp-includes/blocks.php | 4 ++++ src/wp-includes/class-wp-ajax-response.php | 2 ++ src/wp-includes/class-wp-dependencies.php | 2 ++ src/wp-includes/class-wp-widget.php | 4 ++++ src/wp-includes/formatting.php | 6 ++++++ src/wp-includes/functions.php | 12 ++++++++++++ src/wp-includes/link-template.php | 2 ++ src/wp-includes/pluggable.php | 4 ++++ src/wp-includes/plugin.php | 4 ++++ src/wp-includes/post.php | 2 ++ src/wp-includes/rewrite.php | 2 ++ src/wp-includes/script-loader.php | 2 ++ src/wp-includes/shortcodes.php | 6 ++++++ 22 files changed, 76 insertions(+) diff --git a/src/wp-admin/includes/class-wp-comments-list-table.php b/src/wp-admin/includes/class-wp-comments-list-table.php index 2b927a7f81a6a..7294f89d1bd3b 100644 --- a/src/wp-admin/includes/class-wp-comments-list-table.php +++ b/src/wp-admin/includes/class-wp-comments-list-table.php @@ -62,6 +62,8 @@ public function __construct( $args = array() ) { * @param string $name Comment author name. * @param int $comment_id Comment ID. * @return string Avatar with the user name. + * + * @phpstan-return non-falsy-string */ public function floated_admin_avatar( $name, $comment_id ) { $comment = get_comment( $comment_id ); diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index 6134db45b6a95..42de0269a05c2 100644 --- a/src/wp-admin/includes/media.php +++ b/src/wp-admin/includes/media.php @@ -1315,6 +1315,8 @@ function image_size_input_fields( $post, $check = '' ) { * @param WP_Post $post * @param string $url_type * @return string HTML markup for the link URL buttons. + * + * @phpstan-return non-falsy-string */ function image_link_input_fields( $post, $url_type = '' ) { @@ -1348,6 +1350,8 @@ function image_link_input_fields( $post, $url_type = '' ) { * * @param WP_Post $edit_post Attachment WP_Post object. * @return string HTML markup for the textarea element. + * + * @phpstan-return non-falsy-string */ function wp_caption_input_textarea( $edit_post ) { // Post data is already escaped. @@ -1637,6 +1641,8 @@ function get_media_items( $post_id, $errors ) { * @param int $attachment_id Attachment ID for modification. * @param string|array $args Optional. Override defaults. * @return string HTML form for attachment. + * + * @phpstan-return non-falsy-string */ function get_media_item( $attachment_id, $args = null ) { global $redir_tab; @@ -3013,6 +3019,8 @@ function media_upload_library_form( $errors ) { * * @param string $default_view * @return string HTML content of the form. + * + * @phpstan-return non-falsy-string */ function wp_media_insert_url_form( $default_view = 'image' ) { /** This filter is documented in wp-admin/includes/media.php */ diff --git a/src/wp-admin/includes/ms.php b/src/wp-admin/includes/ms.php index 155170aaf4f41..0ae6381ee9855 100644 --- a/src/wp-admin/includes/ms.php +++ b/src/wp-admin/includes/ms.php @@ -1211,6 +1211,8 @@ function get_site_screen_help_tab_args() { * @since 4.9.0 * * @return string Help sidebar content. + * + * @phpstan-return non-falsy-string */ function get_site_screen_help_sidebar_content() { return '

' . __( 'For more information:' ) . '

' . diff --git a/src/wp-admin/includes/plugin.php b/src/wp-admin/includes/plugin.php index a34d473bca650..0788e276875fb 100644 --- a/src/wp-admin/includes/plugin.php +++ b/src/wp-admin/includes/plugin.php @@ -2146,6 +2146,8 @@ function get_plugin_page_hook( $plugin_page, $parent_page ) { * @param string $parent_page The slug name for the parent menu (or the file name of a standard * WordPress admin page). * @return string Hook name for the plugin page. + * + * @phpstan-return non-falsy-string */ function get_plugin_page_hookname( $plugin_page, $parent_page ) { global $admin_page_hooks; diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index 93428dcc4ae6d..bf57e67952396 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -2652,6 +2652,8 @@ function submit_button( $text = '', $type = 'primary', $name = 'submit', $wrap = * e.g. `id="search-submit"`, though the array format is generally preferred. * Default empty string. * @return string Submit button HTML. + * + * @phpstan-return non-falsy-string */ function get_submit_button( $text = '', $type = 'primary large', $name = 'submit', $wrap = true, $other_attributes = '' ) { if ( ! is_array( $type ) ) { diff --git a/src/wp-admin/includes/widgets.php b/src/wp-admin/includes/widgets.php index e751602866b0d..5697a6e9ff0dd 100644 --- a/src/wp-admin/includes/widgets.php +++ b/src/wp-admin/includes/widgets.php @@ -322,6 +322,8 @@ function wp_widget_control( $sidebar_args ) { /** * @param string $classes * @return string Modified body classes. + * + * @phpstan-return non-falsy-string */ function wp_widgets_access_body_class( $classes ) { return "$classes widgets_access "; diff --git a/src/wp-includes/abilities-api.php b/src/wp-includes/abilities-api.php index 393e40b56ed8c..66dd394809a23 100644 --- a/src/wp-includes/abilities-api.php +++ b/src/wp-includes/abilities-api.php @@ -292,6 +292,8 @@ * of ability behavior. * } * @return WP_Ability|null The registered ability instance on success, `null` on failure. + * + * @phpstan-param lowercase-string&non-falsy-string $name */ function wp_register_ability( string $name, array $args ): ?WP_Ability { if ( ! doing_action( 'wp_abilities_api_init' ) ) { diff --git a/src/wp-includes/block-supports/elements.php b/src/wp-includes/block-supports/elements.php index fe9543a5d5474..e8076cc1bd3ba 100644 --- a/src/wp-includes/block-supports/elements.php +++ b/src/wp-includes/block-supports/elements.php @@ -13,6 +13,8 @@ * @access private * * @return string The unique class name. + * + * @phpstan-return lowercase-string&non-falsy-string */ function wp_get_elements_class_name(): string { return wp_unique_prefixed_id( 'wp-elements-' ); diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 78cd1bbdc15f5..67bfe224d972d 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -1491,6 +1491,8 @@ function wp_is_theme_directory_ignored( $path ) { * @since 6.0.0 Adds the whole theme to the export archive. * * @return WP_Error|string Path of the ZIP file or error on failure. + * + * @phpstan-return non-falsy-string|WP_Error */ function wp_generate_block_templates_export_file() { $wp_version = wp_get_wp_version(); diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index beaf08d4022c6..e8e137b21108d 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -3084,6 +3084,8 @@ static function ( $format ) { * @param WP_Block $block Block instance. * @param bool $is_next Flag for handling `next/previous` blocks. * @return string|null The pagination arrow HTML or null if there is none. + * + * @phpstan-return non-falsy-string|null */ function get_query_pagination_arrow( $block, $is_next ) { $arrow_map = array( @@ -3184,6 +3186,8 @@ function build_comment_query_vars_from_block( $block ) { * @param string $pagination_type Optional. Type of the arrow we will be rendering. * Accepts 'next' or 'previous'. Default 'next'. * @return string|null The pagination arrow HTML or null if there is none. + * + * @phpstan-return non-falsy-string|null */ function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) { $arrow_map = array( diff --git a/src/wp-includes/class-wp-ajax-response.php b/src/wp-includes/class-wp-ajax-response.php index ab747618e0fbf..b9946343ac79f 100644 --- a/src/wp-includes/class-wp-ajax-response.php +++ b/src/wp-includes/class-wp-ajax-response.php @@ -63,6 +63,8 @@ public function __construct( $args = '' ) { * element as CDATA. Default empty array. * } * @return string XML response. + * + * @phpstan-return non-falsy-string */ public function add( $args = '' ) { $defaults = array( diff --git a/src/wp-includes/class-wp-dependencies.php b/src/wp-includes/class-wp-dependencies.php index 80559201dda03..04d7b849b9ca2 100644 --- a/src/wp-includes/class-wp-dependencies.php +++ b/src/wp-includes/class-wp-dependencies.php @@ -539,6 +539,8 @@ public function set_group( $handle, $recursion, $group ) { * * @param string[] $load Array of script or style handles to load. * @return string Etag header. + * + * @phpstan-return non-falsy-string */ public function get_etag( $load ) { /* diff --git a/src/wp-includes/class-wp-widget.php b/src/wp-includes/class-wp-widget.php index b131c50db3226..7a1a8ae51d2d9 100644 --- a/src/wp-includes/class-wp-widget.php +++ b/src/wp-includes/class-wp-widget.php @@ -213,6 +213,8 @@ public function WP_Widget( $id_base, $name, $widget_options = array(), $control_ * * @param string $field_name Field name. * @return string Name attribute for `$field_name`. + * + * @phpstan-return non-falsy-string */ public function get_field_name( $field_name ) { $pos = strpos( $field_name, '[' ); @@ -238,6 +240,8 @@ public function get_field_name( $field_name ) { * * @param string $field_name Field name. * @return string ID attribute for `$field_name`. + * + * @phpstan-return non-falsy-string */ public function get_field_id( $field_name ) { $field_name = str_replace( array( '[]', '[', ']' ), array( '', '-', '' ), $field_name ); diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index c39beaf7b1d68..8fed195da836f 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -622,6 +622,8 @@ function wp_html_split( $input ) { * @since 4.4.0 * * @return string The regular expression. + * + * @phpstan-return non-falsy-string */ function get_html_split_regex() { static $regex; @@ -2278,6 +2280,8 @@ function sanitize_title_for_query( $title ) { * When set to 'save', additional entities are converted to hyphens * or stripped entirely. Default 'display'. * @return string The sanitized title. + * + * @phpstan-return lowercase-string */ function sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) { $title = strip_tags( $title ); @@ -2863,6 +2867,8 @@ function backslashit( $value ) { * * @param string $value Value to which trailing slash will be added. * @return string String with trailing slash added. + * + * @phpstan-return non-falsy-string */ function trailingslashit( $value ) { return untrailingslashit( $value ) . '/'; diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 393f50a3aad58..962a91fa7aa41 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -2189,6 +2189,8 @@ function path_is_absolute( $path ) { * @param string $base Base path. * @param string $path Path relative to $base. * @return string The path with the base or absolute path. + * + * @phpstan-return non-falsy-string */ function path_join( $base, $path ) { if ( path_is_absolute( $path ) ) { @@ -2955,6 +2957,8 @@ function _wp_check_existing_file_names( $filename, $files ) { * } * @phpstan-return array{ file: non-empty-string, url: non-empty-string, type: string|false, error: false } * |array{ error: string, ... } + * + * @phpstan-param non-empty-string $name */ function wp_upload_bits( $name, $deprecated, $bits, $time = null ) { if ( ! empty( $deprecated ) ) { @@ -4475,6 +4479,8 @@ function _wp_die_process_input( $message, $title = '', $args = array() ) { * @param int $depth Optional. Maximum depth to walk through $value. Must be * greater than 0. Default 512. * @return string|false The JSON encoded string, or false if it cannot be encoded. + * + * @phpstan-return non-empty-string|false */ function wp_json_encode( $value, $flags = 0, $depth = 512 ) { $json = json_encode( $value, $flags, $depth ); @@ -8172,6 +8178,8 @@ function wp_raise_memory_limit( $context = 'admin' ) { * @since 7.0.0 Uses wp_rand if available. * * @return string UUID. + * + * @phpstan-return lowercase-string&non-falsy-string */ function wp_generate_uuid4() { static $backup_randomizer = false; @@ -9206,6 +9214,8 @@ function clean_dirsize_cache( $path ) { * @since 6.7.0 * * @return string The current WordPress version. + * + * @phpstan-return non-falsy-string */ function wp_get_wp_version() { static $wp_version; @@ -9473,6 +9483,8 @@ function wp_is_heic_image_mime_type( $mime_type ) { * * @param string $message The message to hash. * @return string The hash of the message. + * + * @phpstan-return non-falsy-string */ function wp_fast_hash( #[\SensitiveParameter] diff --git a/src/wp-includes/link-template.php b/src/wp-includes/link-template.php index 3d4ffc23b8f62..c92770ae498fa 100644 --- a/src/wp-includes/link-template.php +++ b/src/wp-includes/link-template.php @@ -4887,6 +4887,8 @@ function get_the_privacy_policy_link( $before = '', $after = '' ) { * @since 6.2.0 * * @return string[] An array of URL hosts. + * + * @phpstan-return array */ function wp_internal_hosts() { static $internal_hosts; diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 4126f25d7c0fc..d13769cf84afe 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -2540,6 +2540,8 @@ function wp_verify_nonce( $nonce, $action = -1 ) { * * @param string|int $action Scalar value to add context to the nonce. * @return string The token. + * + * @phpstan-return lowercase-string&non-falsy-string */ function wp_create_nonce( $action = -1 ) { $user = wp_get_current_user(); @@ -2722,6 +2724,8 @@ function wp_salt( $scheme = 'auth' ) { * @param string $scheme Authentication scheme (auth, secure_auth, logged_in, nonce). * @param string $algo Hashing algorithm to use. Default: 'md5'. * @return string Hash of $data. + * + * @phpstan-return lowercase-string&non-falsy-string */ function wp_hash( $data, $scheme = 'auth', $algo = 'md5' ) { $salt = wp_salt( $scheme ); diff --git a/src/wp-includes/plugin.php b/src/wp-includes/plugin.php index a3e9afacdb8dc..7d04b5fb290e5 100644 --- a/src/wp-includes/plugin.php +++ b/src/wp-includes/plugin.php @@ -376,6 +376,8 @@ function remove_all_filters( $hook_name, $priority = false ) { * @global string[] $wp_current_filter Stores the list of current filters with the current one last * * @return string|false Hook name of the current filter, false if no filter is running. + * + * @phpstan-return non-empty-string|false */ function current_filter() { global $wp_current_filter; @@ -664,6 +666,8 @@ function remove_all_actions( $hook_name, $priority = false ) { * @since 3.9.0 * * @return string|false Hook name of the current action, false if no action is running. + * + * @phpstan-return non-empty-string|false */ function current_action() { return current_filter(); diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 5edc8a50f0299..933c5f874c1bf 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1828,6 +1828,8 @@ function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) * } * @return WP_Post_Type|WP_Error The registered post type object on success, * WP_Error object on failure. + * + * @phpstan-param lowercase-string&non-empty-string $post_type */ function register_post_type( $post_type, $args = array() ) { global $wp_post_types; diff --git a/src/wp-includes/rewrite.php b/src/wp-includes/rewrite.php index 976d2a014b9db..06c4a96507d75 100644 --- a/src/wp-includes/rewrite.php +++ b/src/wp-includes/rewrite.php @@ -247,6 +247,8 @@ function remove_permastruct( $name ) { * @param string $feedname Feed name. Should not start with '_'. * @param callable $callback Callback to run on feed display. * @return string Feed action name. + * + * @phpstan-return non-falsy-string */ function add_feed( $feedname, $callback ) { global $wp_rewrite; diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 6b01efa1718ce..6e726ac9163eb 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 `