From c6a9a31b09ba6d69159513214219c996b9fc5e5e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 08:55:30 +0000 Subject: [PATCH 1/6] Build/Test Tools: Add szepeviktor/phpstan-wordpress as a development dependency. Pulls in the PHPStan extensions maintained for the WordPress ecosystem so that core can register the ones that apply to it, rather than carrying its own copies. Only the package is added here; nothing from it is loaded yet. Its `extension.neon` is deliberately not included, since that bootstraps the `php-stubs/wordpress-stubs` package, which describes the very code core analyzes. Co-authored-by: Pascal Birchler Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013QJ9RphuttTR7cS5G4mU21 --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 06edbbf0e776d..789c7220954b2 100644 --- a/composer.json +++ b/composer.json @@ -53,6 +53,7 @@ "phpcompatibility/phpcompatibility-wp": "~2.1.3", "phpstan/phpstan": "2.2.13", "phpstan/phpstan-phpunit": "2.0.18", + "szepeviktor/phpstan-wordpress": "^2.0.4", "yoast/phpunit-polyfills": "^1.1.0" }, "config": { From feaebf99313379e56576110db3fda410e081197b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 09:09:41 +0000 Subject: [PATCH 2/6] Plugins: Correct `$accepted_args` on three hook registrations. `check_comment_flood_db()`, `wp_render_block_style_variation_support_styles()` and `twenty_twenty_one_post_classes()` declare fewer parameters than the `$accepted_args` they are registered with, so the extra arguments were passed and discarded. Registering them for the arguments they take is what szepeviktor/phpstan-wordpress's `HookCallbackRule` asks for, and leaves nothing for it to report once it is registered in the next commit. The block support lives in Gutenberg as well, where the same change is due. Co-authored-by: Pascal Birchler Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013QJ9RphuttTR7cS5G4mU21 --- .../themes/twentytwentyone/inc/template-functions.php | 2 +- src/wp-includes/block-supports/block-style-variations.php | 2 +- src/wp-includes/default-filters.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-content/themes/twentytwentyone/inc/template-functions.php b/src/wp-content/themes/twentytwentyone/inc/template-functions.php index f141b99a8cf8f..6af17e19ff5d6 100644 --- a/src/wp-content/themes/twentytwentyone/inc/template-functions.php +++ b/src/wp-content/themes/twentytwentyone/inc/template-functions.php @@ -50,7 +50,7 @@ function twenty_twenty_one_post_classes( $classes ) { return $classes; } -add_filter( 'post_class', 'twenty_twenty_one_post_classes', 10, 3 ); +add_filter( 'post_class', 'twenty_twenty_one_post_classes' ); /** * Adds a pingback url auto-discovery header for single posts, pages, or attachments. diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index a808ea3d74ecf..6c863056044b1 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -262,7 +262,7 @@ function wp_enqueue_block_style_variation_styles() { // Register the block support. WP_Block_Supports::get_instance()->register( 'block-style-variation', array() ); -add_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles', 10, 2 ); +add_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles' ); add_filter( 'render_block', 'wp_render_block_style_variation_class_name', 10, 2 ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles', 1 ); diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index d16979c9c8fb5..1191d381dd7b1 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -307,7 +307,7 @@ add_filter( 'pre_kses', 'wp_pre_kses_less_than' ); add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 ); add_filter( 'sanitize_title', 'sanitize_title_with_dashes', 10, 3 ); -add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 ); +add_action( 'check_comment_flood', 'check_comment_flood_db' ); add_filter( 'comment_flood_filter', 'wp_throttle_comment_flood', 10, 3 ); add_filter( 'pre_comment_content', 'wp_rel_ugc', 15 ); From 2eb98b610b9ab43c356f32cddb109529ce70246d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 09:09:41 +0000 Subject: [PATCH 3/6] Build/Test Tools: Register the phpstan-wordpress extensions that apply to core. Loads three services from szepeviktor/phpstan-wordpress in `base.neon`, chosen by measuring each of the package's extensions against `src/` at rule level 10 and at the level CI enforces: * `ShortcodeAttsDynamicFunctionReturnTypeExtension`, which types `shortcode_atts()` from the defaults passed to it. A docblock cannot express that merge. * `HookCallbackRule`, for its check that `$accepted_args` agrees with the callback's signature. Its objection to an action callback that returns a value is ignored in `phpstan.neon.dist`, with the reason recorded there: core registers such functions on actions deliberately, and WordPress discards the value. * `HookDocsRule`, for its check that the type a hook docblock documents accepts the value the hook passes. That documented type is what `apply_filters()` is typed from. The package's other extensions are not loaded. Its hook docblock resolver, visitor and `apply_filters()` extension are what core's own were adapted from, and core's resolve the "This filter is documented in" reference comments. Its remaining return type extensions each do what a conditional `@phpstan-return` does, which core already carries for `wp_parse_url()`, `wp_slash()` and `stripslashes_from_strings_only()`, and loading them changed nothing measurable there. The README records the disposition of every extension and why. The baselines gain the twenty hook docblocks whose documented type does not accept the value passed, and one `get_posts()` call that the shortcode attributes now show is handed a string where it expects an array of IDs. Co-authored-by: Pascal Birchler Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013QJ9RphuttTR7cS5G4mU21 --- phpstan.neon.dist | 18 ++++++ tests/phpstan/README.md | 22 ++++++++ tests/phpstan/base.neon | 36 ++++++++++++ tests/phpstan/baselines/argument.type.neon | 5 ++ .../baselines/parameter.phpDocType.neon | 55 +++++++++++++++++++ 5 files changed, 136 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cc4579365122b..1a3977055857b 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -142,3 +142,21 @@ parameters: message: '#^Parameter \#4 \$length of function substr_compare expects int, null given\.$#' path: src/wp-includes/html-api/class-wp-html-tag-processor.php reportUnmatched: false + + # szepeviktor/phpstan-wordpress's HookCallbackRule, registered in tests/phpstan/base.neon, + # also reports an action callback whose return type is not void. That is sound advice for a + # plugin, where a function returning a value is likely a filter callback registered on the + # wrong function, but core registers functions that happen to return something on actions + # as a matter of course: `wp_save_post_revision()` on `post_updated`, `redirect_canonical()` + # on `template_redirect`, `wp_delete_attachment()` on `importer_scheduled_cleanup`. WordPress + # discards an action callback's return value, so none of the 52 reported registrations is a + # defect, and changing each function's return type to satisfy the rule would change public + # API. The rule's other check, that `$accepted_args` agrees with the callback's signature, is + # kept: it is what the rule is registered for. + # + # The rule reports this under PHPStan's own `return.void` identifier rather than one of its + # own, so the message is matched instead of the identifier to leave PHPStan's `return.void` + # errors reported. + - + message: '#^Action callback returns .+ but should not return anything\.$#' + reportUnmatched: false diff --git a/tests/phpstan/README.md b/tests/phpstan/README.md index 3ab750a069471..4fab23d06d22c 100644 --- a/tests/phpstan/README.md +++ b/tests/phpstan/README.md @@ -106,6 +106,17 @@ Calls whose hook name contains no literal text, such as the `apply_filters_ref_a One consequence worth knowing: because a hook's documentation may live in a different file than the call inheriting it, editing a hook docblock in a file that reference comments point at discards PHPStan's result cache. Every call site inheriting that docblock has to be analyzed again, and PHPStan cannot infer that dependency on its own. +### Extensions from szepeviktor/phpstan-wordpress + +[szepeviktor/phpstan-wordpress](https://github.com/szepeviktor/phpstan-wordpress) is the set of PHPStan extensions maintained for the WordPress ecosystem, and the hook extensions above began as adaptations of it. It is installed as a Composer development dependency so that core can load its extensions directly rather than carry copies of them. Its own `extension.neon` is not included, because that loads [php-stubs/wordpress-stubs](https://github.com/php-stubs/wordpress-stubs), a declaration of every core function and class, which is the code being analyzed here. Instead, [`base.neon`](base.neon) registers the extensions that apply to core one at a time. Installing the package installs the stubs as well; they are not read. + +What the package provides falls into four groups, and only the first is loaded: + +- **Used, because a docblock cannot express what they do.** `ShortcodeAttsDynamicFunctionReturnTypeExtension` types the result of `shortcode_atts()` from the defaults passed to it, the same merge that `wp_parse_args()` performs. `HookCallbackRule` checks a callback registered with `add_action()` or `add_filter()` against its registration: that `$accepted_args` agrees with the parameters the callback declares, and that a filter callback returns a value. Its third check, that an action callback returns nothing, is switched off in [`phpstan.neon.dist`](../../phpstan.neon.dist), where the reason is recorded. `HookDocsRule` checks that the type each `@param` of a hook docblock documents accepts the value the hook passes. That documented type is what `apply_filters()` is typed from, so a wrong one misleads every caller. It reads only a docblock written at the call, so a hook documented elsewhere through a reference comment is not checked. +- **Not used, because core's own versions do the same and more.** `HookDocsVisitor`, `HookDocBlock` and `ApplyFiltersDynamicFunctionReturnTypeExtension` are what the [hook documentation](#hook-documentation) extensions were adapted from. Core's resolve the "This filter is documented in" reference comments, which the originals do not, and core's rules already check the `@param` counts that `HookDocsRule` also checks, so only its type check adds anything. +- **Not used, because a docblock does the same.** `EscSql`, `NormalizeWhitespace`, `StripslashesFromStringsOnly`, `SlashitFunctions`, `WpParseUrl` and `WpSlash` each narrow one function's return type from its arguments in a way a conditional `@phpstan-return` expresses. Where core has that docblock already, as `wp_parse_url()`, `wp_slash()` and `stripslashes_from_strings_only()` do, loading the extension changes nothing; where it does not yet, as `esc_sql()` and `trailingslashit()`, the docblock is the fix to make. A docblock in core also types the function for every plugin, since the stubs are generated from core. phpstan-wordpress itself has gone that way: its 2.x branch dropped the extensions it had for `get_post()`, `get_terms()`, `current_time()`, `wp_die()`, `is_wp_error()` and others in favor of types carried by the stubs, and the [function map](https://github.com/php-stubs/wordpress-stubs/blob/master/functionMap.php) those stubs apply on top of core's docblocks is a list of the ones core could adopt. +- **Not applicable to core.** `WpConstantFetchRule` discourages reading a constant such as `MULTISITE` where a function exists to read it, but core is where those functions read them. `AssertWpErrorTypeSpecifyingExtension` narrows the argument of `assertWPError()` in tests, which are not analyzed; when they are, `@phpstan-assert` on the methods themselves is the way to express it. + ### Errors these rules report These identifiers are specific to WordPress, and can be ignored or baselined like any other error, as described [below](#ignoring-and-baselining-errors). @@ -118,6 +129,17 @@ These identifiers are specific to WordPress, and can be ignored or baselined lik | `wordpress.hookDocReferenceHookMissing` | The referenced file exists, but documents no hook of that name. Either the reference is stale, or the canonical docblock has moved. | | `wordpress.hookParamCountMismatch` | The call passes a different number of arguments than the docblock documents `@param` tags for. Passing fewer risks an `ArgumentCountError` in a callback registered for the documented count; passing more silently drops the extra argument and leaves the documentation misleading. | +The rules loaded from szepeviktor/phpstan-wordpress report under PHPStan's own identifiers rather than ones of their own, so their errors share a baseline with the errors PHPStan itself reports under that identifier. They are told apart by their messages. + +| Identifier | Message | What it means | +| --- | --- | --- | +| `arguments.count` | `Callback expects N parameters, $accepted_args is set to M.` | The `$accepted_args` of an `add_action()` or `add_filter()` call does not fit the callback's signature. Fewer than the callback requires is an `ArgumentCountError` when the hook fires; more than it declares is misleading, and usually a leftover from an earlier signature. | +| `return.missing` | `Filter callback return statement is missing.` | A filter callback returns nothing, so the value being filtered becomes `null`. | +| `return.void` | `Action callback returns X but should not return anything.` | An action callback returns a value. Ignored for core in `phpstan.neon.dist`; see the note there. | +| `parameter.phpDocType` | `@param X $name does not accept actual type of parameter: Y.` | The type a hook docblock documents for a parameter does not accept the value the hook passes. Fix the docblock, or the value; the documented type is what callbacks and the `apply_filters()` return type rely on. | +| `paramTag.count` | `Expected N @param tags, found M.` | The same mismatch `wordpress.hookParamCountMismatch` reports, for a docblock written at the call. | +| `phpDoc.parseError` | `One or more @param tags has an invalid name or invalid syntax.` | A `@param` tag in a hook docblock could not be parsed, or is named `$this`. | + ## Ignoring and baselining errors As we adopt PHPStan iteratively, you may be faced with false positives due to legacy code, or code that is not worth changing at this time. diff --git a/tests/phpstan/base.neon b/tests/phpstan/base.neon index 4cf457f07e0f7..f7a5ad88bc4f2 100644 --- a/tests/phpstan/base.neon +++ b/tests/phpstan/base.neon @@ -62,6 +62,42 @@ services: tags: - phpstan.resultCacheMetaExtension + # The services below come from szepeviktor/phpstan-wordpress, the PHPStan extensions + # maintained for the WordPress ecosystem, installed through Composer. Its own + # extension.neon is deliberately not included: that bootstraps php-stubs/wordpress-stubs, + # which describes the very code analyzed here, so every function and class would be + # declared twice. Instead, the extensions that apply to core are registered one by one. + # See tests/phpstan/README.md for which ones are used, which are not, and why. + + # Types the return value of shortcode_atts() from the defaults it was called with, so + # a shortcode's attributes are an array shape rather than a plain array. The merge + # a call performs is not something a docblock can express. + - + class: SzepeViktor\PHPStan\WordPress\ShortcodeAttsDynamicFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + + # Checks the callback given to add_action() and add_filter() against the call: that + # `$accepted_args` agrees with the number of parameters the callback declares, that + # a filter callback returns a value, and that an action callback does not. The last + # check is switched off for core in phpstan.neon.dist; see the note there. + - + class: SzepeViktor\PHPStan\WordPress\HookCallbackRule + tags: + - phpstan.rules.rule + + # Checks that the type each `@param` of a hook docblock documents accepts the value + # the hook passes. That documented type is what apply_filters() is typed from above, + # so a wrong one misleads every caller. Only a docblock written at the call is checked: + # the rule reads the docblock through phpstan-wordpress's own resolver, registered + # beside it, which does not follow "This filter is documented in" reference comments. + - + class: SzepeViktor\PHPStan\WordPress\HookDocBlock + - + class: SzepeViktor\PHPStan\WordPress\HookDocsRule + tags: + - phpstan.rules.rule + # Runs the visitors above over every file PHPStan parses, not only the ones it # analyzes. # diff --git a/tests/phpstan/baselines/argument.type.neon b/tests/phpstan/baselines/argument.type.neon index 14681f13d929a..9a142a3246b37 100644 --- a/tests/phpstan/baselines/argument.type.neon +++ b/tests/phpstan/baselines/argument.type.neon @@ -1038,6 +1038,11 @@ parameters: identifier: argument.type count: 1 path: ../../../src/wp-includes/load.php + - + message: '#^Parameter \#1 \$args of function get_posts expects array\{numberposts\?\: int, category\?\: int\|string, include\?\: array\, exclude\?\: array\, suppress_filters\?\: bool, \.\.\.\}\|string\|null, array\{include\: non\-falsy\-string, post_status\: ''inherit'', post_type\: ''attachment'', post_mime_type\: ''image'', order\: string, orderby\: string\} given\.$#' + identifier: argument.type + count: 1 + path: ../../../src/wp-includes/media.php - message: '#^Parameter \#5 \$text of function wp_get_attachment_link expects string\|false, bool given\.$#' identifier: argument.type diff --git a/tests/phpstan/baselines/parameter.phpDocType.neon b/tests/phpstan/baselines/parameter.phpDocType.neon index db8a7f3b32466..18eefeafa1126 100644 --- a/tests/phpstan/baselines/parameter.phpDocType.neon +++ b/tests/phpstan/baselines/parameter.phpDocType.neon @@ -18,8 +18,63 @@ parameters: ignoreErrors: + - + message: '#^@param array\ \$inline_edit_statuses does not accept actual type of parameter\: array\{\-1\?\: string, pending\: string, draft\: string\}\|array\{\-1\?\: string, publish\: string, future\: string, private\?\: string, pending\: string, draft\: string\}\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-admin/includes/class-wp-posts-list-table.php + - + message: '#^@param bool \$bulk does not accept actual type of parameter\: 0\|1\.$#' + identifier: parameter.phpDocType + count: 3 + path: ../../../src/wp-admin/includes/class-wp-posts-list-table.php + - + message: '#^@param string \$items does not accept actual type of parameter\: 3\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-admin/includes/dashboard.php + - + message: '#^@param bool \$bool does not accept actual type of parameter\: ''''\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-admin/includes/media.php - message: '#^PHPDoc tag @param for parameter \$block_type with type array\ is incompatible with native type string\.$#' identifier: parameter.phpDocType count: 1 path: ../../../src/wp-includes/class-wp-block-processor.php + - + message: '#^@param stdClass \$details does not accept actual type of parameter\: WP_Site\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-includes/class-wp-site.php + - + message: '#^@param int \$dupe_id does not accept actual type of parameter\: non\-empty\-string\|null\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-includes/comment.php + - + message: '#^@param string \$audio does not accept actual type of parameter\: WP_Post\|null\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-includes/media.php + - + message: '#^@param string \$text does not accept actual type of parameter\: bool\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-includes/media.php + - + message: '#^@param string \$video does not accept actual type of parameter\: WP_Post\|null\.$#' + identifier: parameter.phpDocType + count: 1 + path: ../../../src/wp-includes/media.php + - + message: '#^@param int \$meta_id does not accept actual type of parameter\: non\-empty\-string\|null\.$#' + identifier: parameter.phpDocType + count: 4 + path: ../../../src/wp-includes/meta.php + - + message: '#^@param int \$tt_id does not accept actual type of parameter\: numeric\-string\.$#' + identifier: parameter.phpDocType + count: 4 + path: ../../../src/wp-includes/taxonomy.php From b0367736f8a578d24ae114fbace7e7ab0faa3ff0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 09:57:37 +0000 Subject: [PATCH 4/6] Docs: Correct nineteen hook docblocks whose documented type does not accept the value passed. szepeviktor/phpstan-wordpress's `HookDocsRule`, registered in `base.neon`, reports a hook docblock whose `@param` type does not accept the value the hook is fired with. That documented type is what `apply_filters()` is typed from, so each one misled every caller of the filter as well as every callback written against the documentation. Where the value was the one the documentation promised all along, the value is corrected: `$bulk` in the Quick Edit filters is a bool rather than the loop counter, `disable_captions` is filtered on `false` rather than an empty string, and the IDs handed to `duplicate_comment_id`, `update_{$meta_type}_meta` and `delete_term_taxonomy` are cast to the documented int rather than passed as the strings the database returns. Where the value is right and the documentation was not, the docblock is corrected: `wp_audio_shortcode` and `wp_video_shortcode` pass an attachment post or null rather than a file, `{$adjacent}_image_link` passes `string|false`, `dashboard_secondary_items` an int, and `blog_details` receives either a `WP_Site` or the plain-object copy that `WP_Site::get_details()` makes deliberately. Co-authored-by: Pascal Birchler Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013QJ9RphuttTR7cS5G4mU21 --- .../includes/class-wp-posts-list-table.php | 14 ++--- src/wp-admin/includes/dashboard.php | 2 +- src/wp-admin/includes/media.php | 4 +- src/wp-includes/comment.php | 2 +- src/wp-includes/media.php | 28 +++++----- src/wp-includes/meta.php | 1 + src/wp-includes/ms-blogs.php | 3 +- src/wp-includes/taxonomy.php | 2 +- tests/phpstan/baselines/argument.type.neon | 5 -- .../baselines/parameter.phpDocType.neon | 55 ------------------- 10 files changed, 30 insertions(+), 86 deletions(-) diff --git a/src/wp-admin/includes/class-wp-posts-list-table.php b/src/wp-admin/includes/class-wp-posts-list-table.php index d3d631b9b4467..9957a765dde47 100644 --- a/src/wp-admin/includes/class-wp-posts-list-table.php +++ b/src/wp-admin/includes/class-wp-posts-list-table.php @@ -1856,7 +1856,7 @@ public function inline_edit() { * @param array $users_opt An array of arguments passed to wp_dropdown_users(). * @param bool $bulk A flag to denote if it's a bulk action. */ - $users_opt = apply_filters( 'quick_edit_dropdown_authors_args', $users_opt, $bulk ); + $users_opt = apply_filters( 'quick_edit_dropdown_authors_args', $users_opt, (bool) $bulk ); $authors = wp_dropdown_users( $users_opt ); @@ -1961,7 +1961,7 @@ public function inline_edit() { * @param array $dropdown_args An array of arguments passed to wp_dropdown_pages(). * @param bool $bulk A flag to denote if it's a bulk action. */ - $dropdown_args = apply_filters( 'quick_edit_dropdown_pages_args', $dropdown_args, $bulk ); + $dropdown_args = apply_filters( 'quick_edit_dropdown_pages_args', $dropdown_args, (bool) $bulk ); wp_dropdown_pages( $dropdown_args ); ?> @@ -2108,12 +2108,12 @@ public function inline_edit() { * * @since 6.9.0 * - * @param array $inline_edit_statuses An array of statuses available in the Quick Edit UI. - * @param string $post_type The post type slug. - * @param bool $bulk A flag to denote if it's a bulk action. - * @param bool $can_publish A flag to denote if the user can publish posts. + * @param string[] $inline_edit_statuses An array of statuses available in the Quick Edit UI. + * @param string $post_type The post type slug. + * @param bool $bulk A flag to denote if it's a bulk action. + * @param bool $can_publish A flag to denote if the user can publish posts. */ - $inline_edit_statuses = apply_filters( 'quick_edit_statuses', $inline_edit_statuses, $screen->post_type, $bulk, $can_publish ); + $inline_edit_statuses = apply_filters( 'quick_edit_statuses', $inline_edit_statuses, $screen->post_type, (bool) $bulk, $can_publish ); foreach ( $inline_edit_statuses as $inline_status_value => $inline_status_text ) : ?> diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 488e01aa92481..aafcaa9ac503b 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1622,7 +1622,7 @@ function wp_dashboard_primary() { * * @since 4.4.0 * - * @param string $items How many items to show in the secondary feed. + * @param int $items How many items to show in the secondary feed. */ 'items' => apply_filters( 'dashboard_secondary_items', 3 ), 'show_summary' => 0, diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index be10d2f7bf0b6..a2b7cec7feb52 100644 --- a/src/wp-admin/includes/media.php +++ b/src/wp-admin/includes/media.php @@ -211,9 +211,9 @@ function image_add_caption( $html, $id, $caption, $title, $align, $url, $size, $ * @since 2.6.0 * * @param bool $bool Whether to disable appending captions. Returning true from the filter - * will disable captions. Default empty string. + * will disable captions. Default false. */ - if ( empty( $caption ) || apply_filters( 'disable_captions', '' ) ) { + if ( empty( $caption ) || apply_filters( 'disable_captions', false ) ) { return $html; } diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 817ad17fa97e3..5720a82e5bb08 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -773,7 +773,7 @@ function wp_allow_comment( $commentdata, $wp_error = false ) { wp_unslash( $commentdata['comment_content'] ) ); - $dupe_id = $wpdb->get_var( $dupe ); + $dupe_id = (int) $wpdb->get_var( $dupe ); /** * Filters the ID, if any, of the duplicate comment found when creating a new comment. diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index be0b2fbf098cc..daa2b5e8cfc1b 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -3699,11 +3699,12 @@ function wp_audio_shortcode( $attr, $content = '' ) { * * @since 3.6.0 * - * @param string $html Audio shortcode HTML output. - * @param array $atts Array of audio shortcode attributes. - * @param string $audio Audio file. - * @param int $post_id Post ID. - * @param string $library Media library used for the audio shortcode. + * @param string $html Audio shortcode HTML output. + * @param array $atts Array of audio shortcode attributes. + * @param WP_Post|null $audio Audio attachment post when the shortcode has no source and + * an attached audio file is used, null otherwise. + * @param int $post_id Post ID. + * @param string $library Media library used for the audio shortcode. */ return apply_filters( 'wp_audio_shortcode', $html, $atts, $audio, $post_id, $library ); } @@ -3994,11 +3995,12 @@ function wp_video_shortcode( $attr, $content = '' ) { * * @since 3.6.0 * - * @param string $output Video shortcode HTML output. - * @param array $atts Array of video shortcode attributes. - * @param string $video Video file. - * @param int $post_id Post ID. - * @param string $library Media library used for the video shortcode. + * @param string $output Video shortcode HTML output. + * @param array $atts Array of video shortcode attributes. + * @param WP_Post|null $video Video attachment post when the shortcode has no source and + * an attached video file is used, null otherwise. + * @param int $post_id Post ID. + * @param string $library Media library used for the video shortcode. */ return apply_filters( 'wp_video_shortcode', $output, $atts, $video, $post_id, $library ); } @@ -4072,7 +4074,7 @@ function next_image_link( $size = 'thumbnail', $text = false ) { * @param bool $prev Optional. Whether to display the next (false) or previous (true) link. Default true. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. - * @param bool $text Optional. Link text. Default false. + * @param string|false $text Optional. Link text. Default false. * @return string Markup for image link. */ function get_adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) { @@ -4128,7 +4130,7 @@ function get_adjacent_image_link( $prev = true, $size = 'thumbnail', $text = fal * @param int $attachment_id Attachment ID * @param string|int[] $size Requested image size. Can be any registered image size name, or * an array of width and height values in pixels (in that order). - * @param string $text Link text. + * @param string|false $text Link text, or false for the image itself. */ return apply_filters( "{$adjacent}_image_link", $output, $attachment_id, $size, $text ); } @@ -4143,7 +4145,7 @@ function get_adjacent_image_link( $prev = true, $size = 'thumbnail', $text = fal * @param bool $prev Optional. Whether to display the next (false) or previous (true) link. Default true. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array * of width and height values in pixels (in that order). Default 'thumbnail'. - * @param bool $text Optional. Link text. Default false. + * @param string|false $text Optional. Link text. Default false. */ function adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) { echo get_adjacent_image_link( $prev, $size, $text ); diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 577785c2163a0..4d7caf59a62e4 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -266,6 +266,7 @@ function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_ if ( empty( $meta_ids ) ) { return add_metadata( $meta_type, $object_id, $raw_meta_key, $passed_value ); } + $meta_ids = array_map( 'intval', $meta_ids ); $_meta_value = $meta_value; $meta_value = maybe_serialize( $meta_value ); diff --git a/src/wp-includes/ms-blogs.php b/src/wp-includes/ms-blogs.php index c54563fbbd2b8..f834ceb23a88e 100644 --- a/src/wp-includes/ms-blogs.php +++ b/src/wp-includes/ms-blogs.php @@ -265,7 +265,8 @@ function get_blog_details( $fields = null, $get_all = true ) { * @since MU (3.0.0) * @deprecated 4.7.0 Use {@see 'site_details'} instead. * - * @param WP_Site $details The blog details. + * @param WP_Site|stdClass $details The blog details: a WP_Site from get_blog_details(), or a plain + * object copy of one from WP_Site::get_details(). */ $details = apply_filters_deprecated( 'blog_details', array( $details ), '4.7.0', 'site_details' ); diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 6dbc336395f74..d924e4cc9984e 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -2098,7 +2098,7 @@ function wp_delete_term( $term, $taxonomy, $args = array() ) { return $ids; } - $tt_id = $ids['term_taxonomy_id']; + $tt_id = (int) $ids['term_taxonomy_id']; $defaults = array(); diff --git a/tests/phpstan/baselines/argument.type.neon b/tests/phpstan/baselines/argument.type.neon index 9a142a3246b37..c214bdddc8fbe 100644 --- a/tests/phpstan/baselines/argument.type.neon +++ b/tests/phpstan/baselines/argument.type.neon @@ -1043,11 +1043,6 @@ parameters: identifier: argument.type count: 1 path: ../../../src/wp-includes/media.php - - - message: '#^Parameter \#5 \$text of function wp_get_attachment_link expects string\|false, bool given\.$#' - identifier: argument.type - count: 1 - path: ../../../src/wp-includes/media.php - message: '#^Parameter \#2 \$callback of function array_walk expects callable\(non\-empty\-string\|null, int\<0, max\>\)\: mixed, ''clean_bookmark_cache'' given\.$#' identifier: argument.type diff --git a/tests/phpstan/baselines/parameter.phpDocType.neon b/tests/phpstan/baselines/parameter.phpDocType.neon index 18eefeafa1126..db8a7f3b32466 100644 --- a/tests/phpstan/baselines/parameter.phpDocType.neon +++ b/tests/phpstan/baselines/parameter.phpDocType.neon @@ -18,63 +18,8 @@ parameters: ignoreErrors: - - - message: '#^@param array\ \$inline_edit_statuses does not accept actual type of parameter\: array\{\-1\?\: string, pending\: string, draft\: string\}\|array\{\-1\?\: string, publish\: string, future\: string, private\?\: string, pending\: string, draft\: string\}\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-admin/includes/class-wp-posts-list-table.php - - - message: '#^@param bool \$bulk does not accept actual type of parameter\: 0\|1\.$#' - identifier: parameter.phpDocType - count: 3 - path: ../../../src/wp-admin/includes/class-wp-posts-list-table.php - - - message: '#^@param string \$items does not accept actual type of parameter\: 3\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-admin/includes/dashboard.php - - - message: '#^@param bool \$bool does not accept actual type of parameter\: ''''\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-admin/includes/media.php - message: '#^PHPDoc tag @param for parameter \$block_type with type array\ is incompatible with native type string\.$#' identifier: parameter.phpDocType count: 1 path: ../../../src/wp-includes/class-wp-block-processor.php - - - message: '#^@param stdClass \$details does not accept actual type of parameter\: WP_Site\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-includes/class-wp-site.php - - - message: '#^@param int \$dupe_id does not accept actual type of parameter\: non\-empty\-string\|null\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-includes/comment.php - - - message: '#^@param string \$audio does not accept actual type of parameter\: WP_Post\|null\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-includes/media.php - - - message: '#^@param string \$text does not accept actual type of parameter\: bool\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-includes/media.php - - - message: '#^@param string \$video does not accept actual type of parameter\: WP_Post\|null\.$#' - identifier: parameter.phpDocType - count: 1 - path: ../../../src/wp-includes/media.php - - - message: '#^@param int \$meta_id does not accept actual type of parameter\: non\-empty\-string\|null\.$#' - identifier: parameter.phpDocType - count: 4 - path: ../../../src/wp-includes/meta.php - - - message: '#^@param int \$tt_id does not accept actual type of parameter\: numeric\-string\.$#' - identifier: parameter.phpDocType - count: 4 - path: ../../../src/wp-includes/taxonomy.php From c60fe94c4addd0e163ae9703b13846589cbed9dd Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 09:57:38 +0000 Subject: [PATCH 5/6] Build/Test Tools: Ignore the action-return check of HookCallbackRule inline. `HookCallbackRule` objects to an action callback whose return type is not void. WordPress discards an action callback's return value, and core registers functions that happen to return one on actions as a matter of course, so the objection is not actionable here. Rather than matching the message away in `phpstan.neon.dist`, each of the fifty-two registrations now carries an inline `@phpstan-ignore` saying why, which keeps the decision next to the code it is about and lets a new registration be judged on its own. Co-authored-by: Pascal Birchler Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013QJ9RphuttTR7cS5G4mU21 --- phpstan.neon.dist | 18 ----------------- src/wp-admin/customize.php | 2 ++ src/wp-admin/includes/admin-filters.php | 9 +++++++++ src/wp-admin/includes/class-wp-upgrader.php | 2 ++ src/wp-admin/includes/ms-admin-filters.php | 4 ++++ src/wp-admin/includes/update.php | 2 ++ .../class-wp-customize-setting.php | 1 + src/wp-includes/class-wp-recovery-mode.php | 1 + src/wp-includes/comment.php | 1 + src/wp-includes/cron.php | 2 ++ src/wp-includes/default-filters.php | 20 +++++++++++++++++++ src/wp-includes/ms-default-filters.php | 8 ++++++++ tests/phpstan/README.md | 6 +++--- 13 files changed, 55 insertions(+), 21 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1a3977055857b..cc4579365122b 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -142,21 +142,3 @@ parameters: message: '#^Parameter \#4 \$length of function substr_compare expects int, null given\.$#' path: src/wp-includes/html-api/class-wp-html-tag-processor.php reportUnmatched: false - - # szepeviktor/phpstan-wordpress's HookCallbackRule, registered in tests/phpstan/base.neon, - # also reports an action callback whose return type is not void. That is sound advice for a - # plugin, where a function returning a value is likely a filter callback registered on the - # wrong function, but core registers functions that happen to return something on actions - # as a matter of course: `wp_save_post_revision()` on `post_updated`, `redirect_canonical()` - # on `template_redirect`, `wp_delete_attachment()` on `importer_scheduled_cleanup`. WordPress - # discards an action callback's return value, so none of the 52 reported registrations is a - # defect, and changing each function's return type to satisfy the rule would change public - # API. The rule's other check, that `$accepted_args` agrees with the callback's signature, is - # kept: it is what the rule is registered for. - # - # The rule reports this under PHPStan's own `return.void` identifier rather than one of its - # own, so the message is matched instead of the identifier to leave PHPStan's `return.void` - # errors reported. - - - message: '#^Action callback returns .+ but should not return anything\.$#' - reportUnmatched: false diff --git a/src/wp-admin/customize.php b/src/wp-admin/customize.php index 75c0865b1f8eb..332d7cfc9f13a 100644 --- a/src/wp-admin/customize.php +++ b/src/wp-admin/customize.php @@ -110,8 +110,10 @@ $wp_scripts = new WP_Scripts(); $wp_scripts->registered = $registered; +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'customize_controls_print_scripts', 'print_head_scripts', 20 ); add_action( 'customize_controls_print_footer_scripts', '_wp_footer_scripts' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'customize_controls_print_styles', 'print_admin_styles', 20 ); /** diff --git a/src/wp-admin/includes/admin-filters.php b/src/wp-admin/includes/admin-filters.php index 5337cc02c88c9..3f619d5df2664 100644 --- a/src/wp-admin/includes/admin-filters.php +++ b/src/wp-admin/includes/admin-filters.php @@ -11,6 +11,7 @@ add_action( 'admin_page_access_denied', 'wp_link_manager_disabled_message' ); // Dashboard hooks. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'activity_box_end', 'wp_dashboard_quota' ); add_action( 'welcome_panel', 'wp_welcome_panel' ); @@ -18,9 +19,13 @@ add_action( 'attachment_submitbox_misc_actions', 'attachment_submitbox_metadata' ); add_filter( 'plupload_init', 'wp_show_heic_upload_error' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'media_upload_image', 'wp_media_upload_handler' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'media_upload_audio', 'wp_media_upload_handler' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'media_upload_video', 'wp_media_upload_handler' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'media_upload_file', 'wp_media_upload_handler' ); add_action( 'post-plupload-upload-ui', 'media_upload_flash_bypass' ); @@ -57,10 +62,12 @@ } add_action( 'admin_print_scripts', 'print_emoji_detection_script' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'admin_print_scripts', 'print_head_scripts', 20 ); add_action( 'admin_print_footer_scripts', '_wp_footer_scripts' ); add_action( 'admin_enqueue_scripts', 'wp_enqueue_emoji_styles' ); add_action( 'admin_print_styles', 'print_emoji_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_emoji_styles(). +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'admin_print_styles', 'print_admin_styles', 20 ); add_action( 'admin_print_scripts-index.php', 'wp_localize_community_events' ); @@ -130,10 +137,12 @@ add_action( 'load-plugins.php', 'wp_plugin_update_rows', 20 ); // After wp_update_plugins() is called. add_action( 'load-themes.php', 'wp_theme_update_rows', 20 ); // After wp_update_themes() is called. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'admin_notices', 'update_nag', 3 ); add_action( 'admin_notices', 'deactivated_plugins_notice', 5 ); add_action( 'admin_notices', 'paused_plugins_notice', 5 ); add_action( 'admin_notices', 'paused_themes_notice', 5 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'admin_notices', 'maintenance_nag', 10 ); add_action( 'admin_notices', 'wp_recovery_mode_nag', 1 ); diff --git a/src/wp-admin/includes/class-wp-upgrader.php b/src/wp-admin/includes/class-wp-upgrader.php index ba27113ff73de..2005c1e231dd8 100644 --- a/src/wp-admin/includes/class-wp-upgrader.php +++ b/src/wp-admin/includes/class-wp-upgrader.php @@ -933,6 +933,7 @@ public function run( $options ) { * internally during actions, causing an error because * `WP_Upgrader::restore_temp_backup()` expects an array. */ + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'shutdown', array( $this, 'restore_temp_backup' ), 10, 0 ); } $this->skin->error( $result ); @@ -950,6 +951,7 @@ public function run( $options ) { // Clean up the backup kept in the temporary backup directory. if ( ! empty( $options['hook_extra']['temp_backup'] ) ) { // Delete the backup on `shutdown` to avoid a PHP timeout. + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'shutdown', array( $this, 'delete_temp_backup' ), 100, 0 ); } diff --git a/src/wp-admin/includes/ms-admin-filters.php b/src/wp-admin/includes/ms-admin-filters.php index b1d73825f1f4b..dd894ebfc72ea 100644 --- a/src/wp-admin/includes/ms-admin-filters.php +++ b/src/wp-admin/includes/ms-admin-filters.php @@ -29,11 +29,15 @@ add_filter( 'import_allow_create_users', 'check_import_new_users' ); // Notices hooks. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'admin_notices', 'site_admin_notice' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'network_admin_notices', 'site_admin_notice' ); // Update hooks. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'network_admin_notices', 'update_nag', 3 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'network_admin_notices', 'maintenance_nag', 10 ); // Network Admin hooks. diff --git a/src/wp-admin/includes/update.php b/src/wp-admin/includes/update.php index b0e998264fe06..bc74d0595453b 100644 --- a/src/wp-admin/includes/update.php +++ b/src/wp-admin/includes/update.php @@ -432,6 +432,7 @@ function wp_plugin_update_rows() { $plugins = array_keys( $plugins->response ); foreach ( $plugins as $plugin_file ) { + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( "after_plugin_row_{$plugin_file}", 'wp_plugin_update_row', 10, 2 ); } } @@ -657,6 +658,7 @@ function wp_theme_update_rows() { $themes = array_keys( $themes->response ); foreach ( $themes as $theme ) { + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( "after_theme_row_{$theme}", 'wp_theme_update_row', 10, 2 ); } } diff --git a/src/wp-includes/class-wp-customize-setting.php b/src/wp-includes/class-wp-customize-setting.php index 68dd8b1433857..e5c9615124da2 100644 --- a/src/wp-includes/class-wp-customize-setting.php +++ b/src/wp-includes/class-wp-customize-setting.php @@ -362,6 +362,7 @@ public function preview() { // If the setting does not need previewing now, defer to when it has a value to preview. if ( ! $needs_preview ) { if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) { + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ); } return false; diff --git a/src/wp-includes/class-wp-recovery-mode.php b/src/wp-includes/class-wp-recovery-mode.php index 8fa6bf22cbdea..b1cbf6af8442b 100644 --- a/src/wp-includes/class-wp-recovery-mode.php +++ b/src/wp-includes/class-wp-recovery-mode.php @@ -92,6 +92,7 @@ public function __construct() { public function initialize() { $this->is_initialized = true; + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_logout', array( $this, 'exit_recovery_mode' ) ); add_action( 'login_form_' . self::EXIT_ACTION, array( $this, 'handle_exit_recovery_mode' ) ); add_action( 'recovery_mode_clean_expired_keys', array( $this, 'clean_expired_keys' ) ); diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 5720a82e5bb08..cf7faaa7cb4c2 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2803,6 +2803,7 @@ function wp_set_comment_status( $comment_id, $comment_status, $wp_error = false case 'approve': case '1': $status = '1'; + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_set_comment_status', 'wp_new_comment_notify_postauthor' ); break; case 'spam': diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index 82889d7cc7e2f..61fec7ee4d3fe 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -1022,11 +1022,13 @@ function wp_cron(): void { if ( did_action( 'wp_loaded' ) ) { _wp_cron(); } else { + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_loaded', '_wp_cron', 20 ); } } elseif ( doing_action( 'shutdown' ) ) { _wp_cron(); } else { + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'shutdown', '_wp_cron' ); } } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 1191d381dd7b1..47cc3978dd149 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -118,6 +118,7 @@ add_action( 'admin_init', 'wp_schedule_update_user_counts' ); add_action( 'wp_update_user_counts', 'wp_schedule_update_user_counts', 10, 0 ); foreach ( array( 'user_register', 'deleted_user' ) as $action ) { + // @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( $action, 'wp_maybe_update_user_counts', 10, 0 ); } @@ -357,7 +358,9 @@ add_action( 'publish_future_post', 'check_and_publish_future_post', 10, 1 ); add_action( 'wp_head', 'wp_robots', 1 ); add_action( 'wp_head', 'print_emoji_detection_script', 7 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_head', 'wp_print_styles', 8 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_head', 'wp_print_head_scripts', 9 ); add_action( 'wp_head', 'wp_generator' ); add_action( 'wp_head', 'rel_canonical' ); @@ -391,7 +394,9 @@ // Login actions. add_action( 'login_head', 'wp_robots', 1 ); add_action( 'login_head', 'wp_resource_hints', 8 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'login_head', 'wp_print_head_scripts', 9 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'login_head', 'print_admin_styles', 9 ); add_action( 'login_head', 'wp_site_icon', 99 ); add_action( 'login_footer', 'wp_print_footer_scripts', 20 ); @@ -425,6 +430,7 @@ add_action( 'do_all_pings', 'do_all_pingbacks', 10, 0 ); add_action( 'do_all_pings', 'do_all_enclosures', 10, 0 ); add_action( 'do_all_pings', 'do_all_trackbacks', 10, 0 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'do_all_pings', 'generic_ping', 10, 0 ); // Disable pings (pingbacks, trackbacks, and ping service notifications) in non-production environments. @@ -434,6 +440,7 @@ add_action( 'do_robots', 'do_robots' ); add_action( 'do_favicon', 'do_favicon' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_before_include_template', 'wp_start_template_enhancement_output_buffer', 1000 ); // Late priority to let `wp_template_enhancement_output_buffer` filters and `wp_finalized_template_enhancement_output_buffer` actions be registered. add_action( 'set_comment_cookies', 'wp_set_comment_cookies', 10, 3 ); add_action( 'sanitize_comment_cookies', 'sanitize_comment_cookies' ); @@ -443,6 +450,7 @@ add_action( 'shutdown', 'wp_ob_end_flush_all', 1 ); // Create a revision whenever a post is updated. add_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert', 9, 3 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'post_updated', 'wp_save_post_revision', 10, 1 ); add_action( 'publish_post', '_publish_post_hook', 5, 1 ); add_action( 'transition_post_status', '_transition_post_status', 5, 3 ); @@ -464,7 +472,9 @@ // Cron tasks. add_action( 'wp_scheduled_delete', 'wp_scheduled_delete' ); add_action( 'wp_scheduled_auto_draft_delete', 'wp_delete_auto_drafts' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'importer_scheduled_cleanup', 'wp_delete_attachment' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'upgrader_scheduled_cleanup', 'wp_delete_attachment' ); add_action( 'delete_expired_transients', 'delete_expired_transients' ); @@ -533,7 +543,9 @@ add_action( 'wp_update_comment_type_batch', '_wp_batch_update_comment_type' ); // Email notifications. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'comment_post', 'wp_new_comment_notify_moderator' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'comment_post', 'wp_new_comment_notify_postauthor' ); add_action( 'rest_insert_comment', 'wp_new_comment_via_rest_notify_postauthor' ); add_action( 'rest_insert_comment', 'wp_notify_note_mentions', 10, 3 ); @@ -557,6 +569,7 @@ add_action( 'init', '_wp_connectors_init', 15 ); // Sitemaps actions. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'init', 'wp_sitemaps_get_server' ); /** @@ -685,6 +698,7 @@ add_action( 'change_locale', 'create_initial_taxonomies' ); // Canonical. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'template_redirect', 'redirect_canonical' ); add_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 ); @@ -713,12 +727,16 @@ // Admin Bar. // Don't remove. Wrong way to disable. +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'template_redirect', '_wp_admin_bar_init', 0 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'admin_init', '_wp_admin_bar_init' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_admin_bar_bump_styles' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_admin_bar_header_styles' ); add_action( 'admin_enqueue_scripts', 'wp_enqueue_admin_bar_header_styles' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'before_signup_header', '_wp_admin_bar_init' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'activate_header', '_wp_admin_bar_init' ); add_action( 'wp_body_open', 'wp_admin_bar_render', 0 ); add_action( 'wp_footer', 'wp_admin_bar_render', 1000 ); // Back-compat for themes not using `wp_body_open`. @@ -742,7 +760,9 @@ add_action( 'embed_head', 'print_emoji_detection_script' ); add_action( 'embed_head', 'wp_enqueue_embed_styles', 9 ); add_action( 'embed_head', 'print_embed_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_embed_styles(). +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'embed_head', 'wp_print_head_scripts', 20 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'embed_head', 'wp_print_styles', 20 ); add_action( 'embed_head', 'wp_robots' ); add_action( 'embed_head', 'rel_canonical' ); diff --git a/src/wp-includes/ms-default-filters.php b/src/wp-includes/ms-default-filters.php index 8682d48e18e45..439683a80c89a 100644 --- a/src/wp-includes/ms-default-filters.php +++ b/src/wp-includes/ms-default-filters.php @@ -23,9 +23,12 @@ // Users. add_filter( 'wpmu_validate_user_signup', 'signup_nonce_check' ); add_action( 'init', 'maybe_add_existing_user_to_blog' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wpmu_new_user', 'newuser_notify_siteadmin' ); add_action( 'wpmu_activate_user', 'add_new_user_to_blog', 10, 3 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wpmu_activate_user', 'wpmu_welcome_user_notification', 10, 3 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'after_signup_user', 'wpmu_signup_user_notification', 10, 4 ); add_action( 'network_site_new_created_user', 'wp_send_new_user_notifications' ); add_action( 'network_site_users_created_user', 'wp_send_new_user_notifications' ); @@ -38,7 +41,9 @@ // Blogs. add_filter( 'wpmu_validate_blog_signup', 'signup_nonce_check' ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wpmu_activate_blog', 'wpmu_welcome_notification', 10, 5 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'after_signup_site', 'wpmu_signup_blog_notification', 10, 7 ); add_filter( 'wp_normalize_site_data', 'wp_normalize_site_data', 10, 1 ); add_action( 'wp_validate_site_data', 'wp_validate_site_data', 10, 3 ); @@ -48,9 +53,12 @@ add_action( 'wp_insert_site', 'wp_maybe_transition_site_statuses_on_update', 10, 1 ); add_action( 'wp_update_site', 'wp_maybe_transition_site_statuses_on_update', 10, 2 ); add_action( 'wp_update_site', 'wp_maybe_clean_new_site_cache_on_update', 10, 2 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_initialize_site', 'wp_initialize_site', 10, 2 ); add_action( 'wp_initialize_site', 'wpmu_log_new_registrations', 100, 2 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_initialize_site', 'newblog_notify_siteadmin', 100, 1 ); +// @phpstan-ignore return.void (WordPress discards an action callback's return value.) add_action( 'wp_uninitialize_site', 'wp_uninitialize_site', 10, 1 ); add_action( 'update_blog_public', 'wp_update_blog_public_option_on_site_update', 1, 2 ); diff --git a/tests/phpstan/README.md b/tests/phpstan/README.md index 4fab23d06d22c..6dc5935f2c2e8 100644 --- a/tests/phpstan/README.md +++ b/tests/phpstan/README.md @@ -112,9 +112,9 @@ One consequence worth knowing: because a hook's documentation may live in a diff What the package provides falls into four groups, and only the first is loaded: -- **Used, because a docblock cannot express what they do.** `ShortcodeAttsDynamicFunctionReturnTypeExtension` types the result of `shortcode_atts()` from the defaults passed to it, the same merge that `wp_parse_args()` performs. `HookCallbackRule` checks a callback registered with `add_action()` or `add_filter()` against its registration: that `$accepted_args` agrees with the parameters the callback declares, and that a filter callback returns a value. Its third check, that an action callback returns nothing, is switched off in [`phpstan.neon.dist`](../../phpstan.neon.dist), where the reason is recorded. `HookDocsRule` checks that the type each `@param` of a hook docblock documents accepts the value the hook passes. That documented type is what `apply_filters()` is typed from, so a wrong one misleads every caller. It reads only a docblock written at the call, so a hook documented elsewhere through a reference comment is not checked. +- **Used, because a docblock cannot express what they do.** `ShortcodeAttsDynamicFunctionReturnTypeExtension` types the result of `shortcode_atts()` from the defaults passed to it, the same merge that `wp_parse_args()` performs. `HookCallbackRule` checks a callback registered with `add_action()` or `add_filter()` against its registration: that `$accepted_args` agrees with the parameters the callback declares, and that a filter callback returns a value. Its third check, that an action callback returns nothing, does not fit core, which registers functions that happen to return a value on actions as a matter of course and discards the value; each such registration carries an inline `@phpstan-ignore` saying so. `HookDocsRule` checks that the type each `@param` of a hook docblock documents accepts the value the hook passes. That documented type is what `apply_filters()` is typed from, so a wrong one misleads every caller. It reads only a docblock written at the call, so a hook documented elsewhere through a reference comment is not checked. - **Not used, because core's own versions do the same and more.** `HookDocsVisitor`, `HookDocBlock` and `ApplyFiltersDynamicFunctionReturnTypeExtension` are what the [hook documentation](#hook-documentation) extensions were adapted from. Core's resolve the "This filter is documented in" reference comments, which the originals do not, and core's rules already check the `@param` counts that `HookDocsRule` also checks, so only its type check adds anything. -- **Not used, because a docblock does the same.** `EscSql`, `NormalizeWhitespace`, `StripslashesFromStringsOnly`, `SlashitFunctions`, `WpParseUrl` and `WpSlash` each narrow one function's return type from its arguments in a way a conditional `@phpstan-return` expresses. Where core has that docblock already, as `wp_parse_url()`, `wp_slash()` and `stripslashes_from_strings_only()` do, loading the extension changes nothing; where it does not yet, as `esc_sql()` and `trailingslashit()`, the docblock is the fix to make. A docblock in core also types the function for every plugin, since the stubs are generated from core. phpstan-wordpress itself has gone that way: its 2.x branch dropped the extensions it had for `get_post()`, `get_terms()`, `current_time()`, `wp_die()`, `is_wp_error()` and others in favor of types carried by the stubs, and the [function map](https://github.com/php-stubs/wordpress-stubs/blob/master/functionMap.php) those stubs apply on top of core's docblocks is a list of the ones core could adopt. +- **Not used, because a docblock does the same.** `EscSql`, `NormalizeWhitespace`, `StripslashesFromStringsOnly`, `SlashitFunctions`, `WpParseUrl` and `WpSlash` each narrow one function's return type from its arguments in a way a conditional `@phpstan-return` expresses. Where core has that docblock already, as `wp_parse_url()`, `wp_slash()` and `stripslashes_from_strings_only()` do, loading the extension changes nothing; where it does not yet, as `esc_sql()`, the docblock is the fix to make. A docblock in core also types the function for every plugin, since the stubs are generated from core. phpstan-wordpress itself has gone that way: its 2.x branch dropped the extensions it had for `get_post()`, `get_terms()`, `current_time()`, `wp_die()`, `is_wp_error()` and others in favor of types carried by the stubs. The [function map](https://github.com/php-stubs/wordpress-stubs/blob/master/functionMap.php) those stubs applied on top of core's docblocks has since been brought into core itself, as `@phpstan-param` and `@phpstan-return` tags on the functions it named, so the same types now reach core's own analysis, the stubs, and every plugin from one place. - **Not applicable to core.** `WpConstantFetchRule` discourages reading a constant such as `MULTISITE` where a function exists to read it, but core is where those functions read them. `AssertWpErrorTypeSpecifyingExtension` narrows the argument of `assertWPError()` in tests, which are not analyzed; when they are, `@phpstan-assert` on the methods themselves is the way to express it. ### Errors these rules report @@ -135,7 +135,7 @@ The rules loaded from szepeviktor/phpstan-wordpress report under PHPStan's own i | --- | --- | --- | | `arguments.count` | `Callback expects N parameters, $accepted_args is set to M.` | The `$accepted_args` of an `add_action()` or `add_filter()` call does not fit the callback's signature. Fewer than the callback requires is an `ArgumentCountError` when the hook fires; more than it declares is misleading, and usually a leftover from an earlier signature. | | `return.missing` | `Filter callback return statement is missing.` | A filter callback returns nothing, so the value being filtered becomes `null`. | -| `return.void` | `Action callback returns X but should not return anything.` | An action callback returns a value. Ignored for core in `phpstan.neon.dist`; see the note there. | +| `return.void` | `Action callback returns X but should not return anything.` | An action callback returns a value. WordPress discards it, so where core registers such a function deliberately the call carries an inline `@phpstan-ignore` saying so. | | `parameter.phpDocType` | `@param X $name does not accept actual type of parameter: Y.` | The type a hook docblock documents for a parameter does not accept the value the hook passes. Fix the docblock, or the value; the documented type is what callbacks and the `apply_filters()` return type rely on. | | `paramTag.count` | `Expected N @param tags, found M.` | The same mismatch `wordpress.hookParamCountMismatch` reports, for a docblock written at the call. | | `phpDoc.parseError` | `One or more @param tags has an invalid name or invalid syntax.` | A `@param` tag in a hook docblock could not be parsed, or is named `$this`. | From 8338da92f60f189f139d2334dd7aae7fbf5606f9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 10:00:36 +0000 Subject: [PATCH 6/6] Docs: Bring the types from php-stubs/wordpress-stubs' function map into core. php-stubs/wordpress-stubs, from which every plugin's static analysis takes its view of core, applies a map of PHPStan types on top of core's docblocks when it generates the stubs: conditional return types, narrowed parameters, templates, purity and a few class-level annotations. szepeviktor/phpstan-wordpress dropped its own return type extensions in favor of that map. Each entry is a type core could carry itself, and one carried here reaches core's own analysis, the stubs, and every plugin from one place. This adds the map's entries to the docblocks of the 290 functions, methods, properties and classes it names, as `@phpstan-` tags. An entry is left out where core already carries a `@phpstan-return` or template for the symbol, where the parameter or return is documented with hash notation that `HashNotationVisitor` derives a shape from, or where the symbol lives in Gutenberg. A few entries were wrong for core and are adjusted or dropped: `add_shortcode()` callbacks receive a string rather than an array when a shortcode has no attributes, `_get_list_table()` accepts a `WP_Screen` as well as a name, `get_tag_regex()` and `wp_get_inline_script_tag()` do return an empty string, `wp_update_comment()` returns the row count `wpdb::update()` does, `size_format()` and `get_tags()` are stated in terms of what core's parameter and `get_terms()` types allow, `get_html_split_regex()`, `block_version()` and `wp_is_uuid()` are not pure, and the `WP_REST_Request` generics report every offset assignment in core as an error and are not adopted. Five calls the new types show to be passing something other than the documented value are corrected: `wp_upload_bits()` and `add_option()` are given the value their deprecated parameter documents, `iframe_header()` is no longer given a deprecated argument, `switch_to_blog()` is given the site ID as an int, and `get_term_to_edit()` documents the `WP_Term` it returns. The baselines lose 53 entries the new types resolve, including every `parameter.defaultValue` and `method.nonObject` error, and gain 8: seven reworded, and one `WP_Term_Query::populate_terms()` assignment on what `get_term()` may return that is worth a look of its own. Co-authored-by: Pascal Birchler Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013QJ9RphuttTR7cS5G4mU21 --- phpstan.neon.dist | 2 - src/wp-admin/includes/bookmark.php | 13 +++ .../includes/class-custom-background.php | 7 ++ .../includes/class-custom-image-header.php | 15 +++ .../includes/class-wp-comments-list-table.php | 2 + src/wp-admin/includes/class-wp-list-table.php | 3 + src/wp-admin/includes/image.php | 2 +- src/wp-admin/includes/list-table.php | 9 ++ src/wp-admin/includes/media.php | 12 ++ src/wp-admin/includes/menu.php | 5 + src/wp-admin/includes/meta-boxes.php | 2 + src/wp-admin/includes/ms.php | 4 + src/wp-admin/includes/nav-menu.php | 10 ++ src/wp-admin/includes/plugin-install.php | 2 + src/wp-admin/includes/plugin.php | 33 ++++++ src/wp-admin/includes/post.php | 2 + src/wp-admin/includes/taxonomy.php | 10 ++ src/wp-admin/includes/template.php | 8 ++ src/wp-admin/includes/upgrade.php | 4 +- src/wp-admin/includes/widgets.php | 2 + src/wp-admin/update.php | 2 +- src/wp-includes/abilities-api.php | 2 + src/wp-includes/author-template.php | 7 ++ src/wp-includes/block-patterns.php | 2 + src/wp-includes/block-supports/elements.php | 2 + src/wp-includes/block-supports/typography.php | 2 + src/wp-includes/block-template-utils.php | 4 + src/wp-includes/blocks.php | 6 + src/wp-includes/bookmark.php | 13 +++ src/wp-includes/category-template.php | 6 + src/wp-includes/category.php | 46 ++++++++ src/wp-includes/class-wp-ajax-response.php | 2 + src/wp-includes/class-wp-block-list.php | 13 +++ src/wp-includes/class-wp-block-supports.php | 3 + src/wp-includes/class-wp-dependencies.php | 14 +++ src/wp-includes/class-wp-http.php | 27 +++++ src/wp-includes/class-wp-locale.php | 4 + src/wp-includes/class-wp-object-cache.php | 2 + src/wp-includes/class-wp-query.php | 4 + .../class-wp-theme-json-resolver.php | 2 + src/wp-includes/class-wp-theme.php | 20 ++++ src/wp-includes/class-wp-widget-factory.php | 6 + src/wp-includes/class-wp-widget.php | 35 ++++++ src/wp-includes/class-wpdb.php | 4 + src/wp-includes/comment-template.php | 5 + src/wp-includes/comment.php | 8 ++ src/wp-includes/cron.php | 23 ++++ src/wp-includes/deprecated.php | 13 +++ src/wp-includes/embed.php | 2 +- src/wp-includes/feed.php | 6 + src/wp-includes/formatting.php | 49 ++++++++ src/wp-includes/functions.php | 72 ++++++++++++ src/wp-includes/general-template.php | 8 ++ src/wp-includes/http.php | 76 +++++++++++++ src/wp-includes/kses.php | 2 + src/wp-includes/l10n.php | 4 + .../l10n/class-wp-translations.php | 7 ++ src/wp-includes/link-template.php | 10 ++ src/wp-includes/load.php | 30 +++++ src/wp-includes/meta.php | 2 + src/wp-includes/ms-blogs.php | 6 + src/wp-includes/ms-functions.php | 6 + src/wp-includes/ms-site.php | 6 + src/wp-includes/nav-menu.php | 2 + src/wp-includes/option.php | 8 ++ src/wp-includes/pluggable.php | 20 ++++ src/wp-includes/plugin.php | 30 +++++ src/wp-includes/post-template.php | 4 + src/wp-includes/post.php | 2 + src/wp-includes/rest-api.php | 12 ++ src/wp-includes/revision.php | 8 ++ src/wp-includes/rewrite.php | 3 + src/wp-includes/robots-template.php | 18 +++ src/wp-includes/script-loader.php | 7 ++ src/wp-includes/shortcodes.php | 15 +++ src/wp-includes/taxonomy.php | 31 +++++- src/wp-includes/user.php | 8 ++ src/wp-includes/widgets.php | 36 ++++++ tests/phpstan/baselines/argument.type.neon | 11 +- tests/phpstan/baselines/arguments.count.neon | 20 ---- .../baselines/deadCode.unreachable.neon | 2 +- tests/phpstan/baselines/if.alwaysFalse.neon | 5 - tests/phpstan/baselines/isset.property.neon | 4 +- tests/phpstan/baselines/method.nonObject.neon | 55 --------- .../baselines/parameter.defaultValue.neon | 105 ------------------ .../phpstan/baselines/property.nonObject.neon | 30 ----- .../phpstan/baselines/property.notFound.neon | 35 +----- tests/phpstan/baselines/property.private.neon | 10 -- .../phpstan/baselines/property.protected.neon | 6 +- .../phpstan/baselines/return.unusedType.neon | 10 -- 90 files changed, 921 insertions(+), 286 deletions(-) delete mode 100644 tests/phpstan/baselines/method.nonObject.neon delete mode 100644 tests/phpstan/baselines/parameter.defaultValue.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cc4579365122b..b1240bc16b1f7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -54,7 +54,6 @@ includes: - tests/phpstan/baselines/isset.offset.neon - tests/phpstan/baselines/isset.property.neon - tests/phpstan/baselines/method.childParameterType.neon - - tests/phpstan/baselines/method.nonObject.neon - tests/phpstan/baselines/method.unused.neon - tests/phpstan/baselines/notIdentical.alwaysTrue.neon - tests/phpstan/baselines/nullCoalesce.offset.neon @@ -62,7 +61,6 @@ includes: - tests/phpstan/baselines/offsetAccess.nonOffsetAccessible.neon - tests/phpstan/baselines/offsetAccess.notFound.neon - tests/phpstan/baselines/offsetAssign.valueType.neon - - tests/phpstan/baselines/parameter.defaultValue.neon - tests/phpstan/baselines/parameter.notFound.neon - tests/phpstan/baselines/parameter.phpDocType.neon - tests/phpstan/baselines/parameterByRef.type.neon diff --git a/src/wp-admin/includes/bookmark.php b/src/wp-admin/includes/bookmark.php index c64bac144c588..f8f8dd68b291b 100644 --- a/src/wp-admin/includes/bookmark.php +++ b/src/wp-admin/includes/bookmark.php @@ -12,6 +12,8 @@ * @since 2.0.0 * * @return int The link ID on success. The value 0 on failure. + * + * @phpstan-return int<0, max> */ function add_link() { return edit_link(); @@ -24,6 +26,8 @@ function add_link() { * * @param int $link_id Optional. ID of the link to edit. Default 0. * @return int The link ID on success. The value 0 on failure. + * + * @phpstan-return int<0, max> */ function edit_link( $link_id = 0 ) { if ( ! current_user_can( 'manage_links' ) ) { @@ -56,6 +60,9 @@ function edit_link( $link_id = 0 ) { * @since 2.0.0 * * @return stdClass Default link object. + * + * @phpstan-impure + * @phpstan-return object{link_url: string, link_name: string, link_visible: 'Y'}&stdClass */ function get_default_link_to_edit() { $link = new stdClass(); @@ -122,6 +129,8 @@ function wp_delete_link( $link_id ) { * * @param int $link_id Link ID to look up. * @return int[] The IDs of the requested link's categories. + * + * @phpstan-return ($link_id is empty ? array{} : array>) */ function wp_get_link_cats( $link_id = 0 ) { $cats = wp_get_object_terms( $link_id, 'link_category', array( 'fields' => 'ids' ) ); @@ -170,6 +179,8 @@ function get_link_to_edit( $link ) { * } * @param bool $wp_error Optional. Whether to return a WP_Error object on failure. Default false. * @return int|WP_Error The link ID on success. The value 0 or WP_Error on failure. + * + * @phpstan-return ($wp_error is false ? int<0, max> : int<0, max>|WP_Error) */ function wp_insert_link( $linkdata, $wp_error = false ) { global $wpdb; @@ -296,6 +307,8 @@ function wp_set_link_cats( $link_id = 0, $link_categories = array() ) { * * @param array $linkdata Link data to update. See wp_insert_link() for accepted arguments. * @return int The updated link ID on success. The value 0 on failure. + * + * @phpstan-return int<0, max> */ function wp_update_link( $linkdata ) { $link_id = (int) $linkdata['link_id']; diff --git a/src/wp-admin/includes/class-custom-background.php b/src/wp-admin/includes/class-custom-background.php index 7f8f23485086a..7e318658da48b 100644 --- a/src/wp-admin/includes/class-custom-background.php +++ b/src/wp-admin/includes/class-custom-background.php @@ -19,6 +19,8 @@ class Custom_Background { * * @since 3.0.0 * @var callable + * + * @phpstan-var ''|callable(): void */ public $admin_header_callback; @@ -27,6 +29,8 @@ class Custom_Background { * * @since 3.0.0 * @var callable + * + * @phpstan-var ''|callable(): void */ public $admin_image_div_callback; @@ -47,6 +51,9 @@ class Custom_Background { * Default empty string. * @param callable $admin_image_div_callback Optional. Custom image div output callback. * Default empty string. + * + * @phpstan-param ''|callable(): void $admin_header_callback + * @phpstan-param ''|callable(): void $admin_image_div_callback */ public function __construct( $admin_header_callback = '', $admin_image_div_callback = '' ) { $this->admin_header_callback = $admin_header_callback; diff --git a/src/wp-admin/includes/class-custom-image-header.php b/src/wp-admin/includes/class-custom-image-header.php index c1816ffae2d9c..de0e8932714f7 100644 --- a/src/wp-admin/includes/class-custom-image-header.php +++ b/src/wp-admin/includes/class-custom-image-header.php @@ -19,6 +19,8 @@ class Custom_Image_Header { * * @since 2.1.0 * @var callable + * + * @phpstan-var ''|callable(): void */ public $admin_header_callback; @@ -27,6 +29,8 @@ class Custom_Image_Header { * * @since 3.0.0 * @var callable + * + * @phpstan-var ''|callable(): void */ public $admin_image_div_callback; @@ -54,6 +58,8 @@ class Custom_Image_Header { * @param callable $admin_header_callback Administration header callback. * @param callable $admin_image_div_callback Optional. Custom image div output callback. * Default empty string. + * + * @phpstan-param ''|callable(): void $admin_image_div_callback */ public function __construct( $admin_header_callback, $admin_image_div_callback = '' ) { $this->admin_header_callback = $admin_header_callback; @@ -310,6 +316,8 @@ public function process_default_headers() { * * @param string $type The header type. One of 'default' (for the Uploaded Images control) * or 'uploaded' (for the Uploaded Images control). + * + * @phpstan-param 'default'|'uploaded' $type */ public function show_header_selector( $type = 'default' ) { if ( 'default' === $type ) { @@ -1169,6 +1177,13 @@ public function filter_upload_tabs( $tabs ) { * registered for that theme; and the key of an image uploaded for that theme * (the attachment ID of the image). Or an array of arguments: attachment_id, * url, width, height. All are required. + * + * @phpstan-param string|array{ + * attachment_id: int<1, max>, + * url: string, + * width: int<0, max>, + * height: int<0, max>, + * } $choice */ final public function set_header_image( $choice ) { if ( is_array( $choice ) || is_object( $choice ) ) { 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/class-wp-list-table.php b/src/wp-admin/includes/class-wp-list-table.php index df8e71834e0db..84f69c1f4c511 100644 --- a/src/wp-admin/includes/class-wp-list-table.php +++ b/src/wp-admin/includes/class-wp-list-table.php @@ -319,6 +319,9 @@ public function prepare_items() { * @since 3.1.0 * * @param array|string $args Array or string of arguments with information about the pagination. + * + * @phpstan-param array{total_items?: int, total_pages?: int, per_page?: int} $args + * @phpstan-return void */ protected function set_pagination_args( $args ) { $args = wp_parse_args( diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index 935c613d561e9..c3a4a8d769be0 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -635,7 +635,7 @@ function wp_generate_attachment_metadata( $attachment_id, $file ) { break; } $basename = str_replace( '.', '-', wp_basename( $file ) ) . '-image' . $ext; - $uploaded = wp_upload_bits( $basename, '', $metadata['image']['data'] ); + $uploaded = wp_upload_bits( $basename, null, $metadata['image']['data'] ); if ( false === $uploaded['error'] ) { $image_attachment = array( 'post_mime_type' => $metadata['image']['mime'], diff --git a/src/wp-admin/includes/list-table.php b/src/wp-admin/includes/list-table.php index 97dfe4f858c7e..ed1fd61a40050 100644 --- a/src/wp-admin/includes/list-table.php +++ b/src/wp-admin/includes/list-table.php @@ -17,6 +17,15 @@ * @param string $class_name The type of the list table, which is the class name. * @param array $args Optional. Arguments to pass to the class. Accepts 'screen'. * @return WP_List_Table|false List table object on success, false if the class does not exist. + * + * @phpstan-template T of string + * @phpstan-param T $class_name + * @phpstan-param array{screen?: string|WP_Screen|null} $args + * @phpstan-return ( + * $class_name is 'WP_Posts_List_Table'|'WP_Media_List_Table'|'WP_Terms_List_Table'|'WP_Users_List_Table'|'WP_Comments_List_Table'|'WP_Post_Comments_List_Table'|'WP_Links_List_Table'|'WP_Plugin_Install_List_Table'|'WP_Themes_List_Table'|'WP_Theme_Install_List_Table'|'WP_Plugins_List_Table'|'WP_Application_Passwords_List_Table'|'WP_MS_Sites_List_Table'|'WP_MS_Users_List_Table'|'WP_MS_Themes_List_Table'|'WP_Privacy_Data_Export_Requests_List_Table'|'WP_Privacy_Data_Removal_Requests_List_Table' + * ? new + * : false + * ) */ function _get_list_table( $class_name, $args = array() ) { $core_classes = array( diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index a2b7cec7feb52..09b244f3dbf11 100644 --- a/src/wp-admin/includes/media.php +++ b/src/wp-admin/includes/media.php @@ -1238,6 +1238,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 = '' ) { /** @@ -1316,6 +1318,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 = '' ) { @@ -1349,6 +1353,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. @@ -1638,6 +1644,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; @@ -1932,6 +1940,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 ); @@ -3014,6 +3024,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/menu.php b/src/wp-admin/includes/menu.php index a95cf9e33956e..01bf5a046308b 100644 --- a/src/wp-admin/includes/menu.php +++ b/src/wp-admin/includes/menu.php @@ -208,6 +208,11 @@ * @param string $class_to_add The CSS class to add. * @param string $classes The string to add the CSS class to. * @return string The string with the CSS class added. + * + * @phpstan-template T of string + * @phpstan-param T $class_to_add + * @phpstan-pure + * @phpstan-return ($classes is empty ? T : non-empty-string) */ function add_cssclass( $class_to_add, $classes ) { if ( empty( $classes ) ) { diff --git a/src/wp-admin/includes/meta-boxes.php b/src/wp-admin/includes/meta-boxes.php index 535a00cd3fe94..9149f50daadd8 100644 --- a/src/wp-admin/includes/meta-boxes.php +++ b/src/wp-admin/includes/meta-boxes.php @@ -1257,6 +1257,8 @@ function link_target_meta_box( $link ) { * if it matches the current link's relationship. * Default empty string. * @param mixed $deprecated Deprecated. Not used. + * + * @phpstan-param '' $deprecated */ function xfn_check( $xfn_relationship, $xfn_value = '', $deprecated = '' ) { global $link; diff --git a/src/wp-admin/includes/ms.php b/src/wp-admin/includes/ms.php index 56e17113653d2..1bd642f704387 100644 --- a/src/wp-admin/includes/ms.php +++ b/src/wp-admin/includes/ms.php @@ -1186,6 +1186,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( @@ -1210,6 +1212,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/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/plugin-install.php b/src/wp-admin/includes/plugin-install.php index 62e93a36eef5d..b780fa69c8e67 100644 --- a/src/wp-admin/includes/plugin-install.php +++ b/src/wp-admin/includes/plugin-install.php @@ -309,6 +309,8 @@ function install_dashboard() { * @since 4.6.0 The `$type_selector` parameter was deprecated. * * @param bool $deprecated Not used. + * + * @phpstan-param true $deprecated */ function install_search_form( $deprecated = true ) { $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; diff --git a/src/wp-admin/includes/plugin.php b/src/wp-admin/includes/plugin.php index 9969316ab8d58..29a67dd8d5d0f 100644 --- a/src/wp-admin/includes/plugin.php +++ b/src/wp-admin/includes/plugin.php @@ -900,6 +900,9 @@ function activate_plugins( $plugins, $redirect = '', $network_wide = false, $sil * @param string $deprecated Not used. * @return bool|null|WP_Error True on success, false if `$plugins` is empty, `WP_Error` on failure. * `null` if filesystem credentials are required to proceed. + * + * @phpstan-param '' $deprecated + * @phpstan-return ($plugins is empty ? false : true|null|WP_Error) */ function delete_plugins( $plugins, $deprecated = '' ) { global $wp_filesystem; @@ -1105,6 +1108,8 @@ function validate_active_plugins() { * * @param string $plugin Path to the plugin file relative to the plugins directory. * @return int|WP_Error 0 on success, WP_Error on failure. + * + * @phpstan-return ($plugin is empty ? WP_Error : 0|WP_Error) */ function validate_plugin( $plugin ) { if ( validate_file( $plugin ) ) { @@ -1388,6 +1393,8 @@ function uninstall_plugin( $plugin ) { * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added via CSS. * @param int|float $position Optional. The position in the menu order this item should appear. * @return string The resulting page's hook_suffix. + * + * @phpstan-param ''|callable $callback */ function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $icon_url = '', $position = null ) { global $menu, $admin_page_hooks, $_registered_pages, $_parent_pages; @@ -1483,6 +1490,8 @@ function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $call * @param callable $callback Optional. The function to be called to output the content for this page. * @param int|float $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { global $submenu, $menu, $_wp_real_parent_file, $_wp_submenu_nopriv, @@ -1596,6 +1605,8 @@ function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_management_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'tools.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1620,6 +1631,8 @@ function add_management_page( $page_title, $menu_title, $capability, $menu_slug, * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_options_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'options-general.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1644,6 +1657,8 @@ function add_options_page( $page_title, $menu_title, $capability, $menu_slug, $c * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'themes.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1668,6 +1683,8 @@ function add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $cal * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_plugins_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'plugins.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1692,6 +1709,8 @@ function add_plugins_page( $page_title, $menu_title, $capability, $menu_slug, $c * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_users_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { if ( current_user_can( 'edit_users' ) ) { @@ -1721,6 +1740,8 @@ function add_users_page( $page_title, $menu_title, $capability, $menu_slug, $cal * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_dashboard_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'index.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1745,6 +1766,8 @@ function add_dashboard_page( $page_title, $menu_title, $capability, $menu_slug, * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_posts_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'edit.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1769,6 +1792,8 @@ function add_posts_page( $page_title, $menu_title, $capability, $menu_slug, $cal * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_media_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'upload.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1793,6 +1818,8 @@ function add_media_page( $page_title, $menu_title, $capability, $menu_slug, $cal * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_links_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'link-manager.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1817,6 +1844,8 @@ function add_links_page( $page_title, $menu_title, $capability, $menu_slug, $cal * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_pages_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'edit.php?post_type=page', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -1841,6 +1870,8 @@ function add_pages_page( $page_title, $menu_title, $capability, $menu_slug, $cal * @param callable $callback Optional. The function to be called to output the content for this page. * @param int $position Optional. The position in the menu order this item should appear. * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. + * + * @phpstan-param ''|callable $callback */ function add_comments_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) { return add_submenu_page( 'edit-comments.php', $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); @@ -2136,6 +2167,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/post.php b/src/wp-admin/includes/post.php index 40a51e63945b3..2ce642cc2b02f 100644 --- a/src/wp-admin/includes/post.php +++ b/src/wp-admin/includes/post.php @@ -1206,6 +1206,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-admin/includes/taxonomy.php b/src/wp-admin/includes/taxonomy.php index 470d36d55ffb1..85c7095183d7c 100644 --- a/src/wp-admin/includes/taxonomy.php +++ b/src/wp-admin/includes/taxonomy.php @@ -117,6 +117,8 @@ function wp_create_categories( $categories, $post_id = 0 ) { * @param bool $wp_error Optional. Default false. * @return int|WP_Error The ID number of the new or updated Category on success. Zero or a WP_Error on failure, * depending on param `$wp_error`. + * + * @phpstan-return ($wp_error is false ? int<0, max> : int<1, max>|WP_Error) */ function wp_insert_category( $catarr, $wp_error = false ) { $cat_defaults = array( @@ -184,6 +186,8 @@ function wp_insert_category( $catarr, $wp_error = false ) { * * @param array $catarr The 'cat_ID' value is required. All other keys are optional. * @return int|false The ID number of the new or updated Category on success. Zero or FALSE on failure. + * + * @phpstan-return int<0, max>|false */ function wp_update_category( $catarr ) { $cat_id = (int) $catarr['cat_ID']; @@ -218,6 +222,12 @@ 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) + * ) */ function tag_exists( $tag_name ) { return term_exists( $tag_name, 'post_tag' ); diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index a24aae32cc8dd..b29b7d481b489 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -1302,6 +1302,8 @@ function _get_plugin_from_callback( $callback ) { * Often this is the object that's the focus of the current screen, * for example a `WP_Post` or `WP_Comment` object. * @return int Number of meta_boxes. + * + * @phpstan-return int<0, max> */ function do_meta_boxes( $screen, $context, $data_object ) { global $wp_meta_boxes; @@ -1708,6 +1710,8 @@ function add_settings_section( $id, $title, $callback, $page, $args = array() ) * @type string $class CSS Class to be added to the `` element when the * field is output. * } + * + * @phpstan-return void */ function add_settings_field( $id, $title, $callback, $page, $section = 'default', $args = array() ) { global $wp_settings_fields; @@ -2124,6 +2128,8 @@ function _admin_search_query() { * * @param string $title Optional. Title of the Iframe page. Default empty. * @param bool $deprecated Not used. + * + * @phpstan-param false $deprecated */ function iframe_header( $title = '', $deprecated = false ) { global $hook_suffix, $admin_body_class, $body_id, $wp_locale; @@ -2633,6 +2639,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/upgrade.php b/src/wp-admin/includes/upgrade.php index 244401cf24013..461ab78a219e5 100644 --- a/src/wp-admin/includes/upgrade.php +++ b/src/wp-admin/includes/upgrade.php @@ -43,6 +43,8 @@ * @type string $password The password of the site owner, if their user account didn't already exist. * @type string $password_message The explanatory message regarding the password. * } + * + * @phpstan-param '' $deprecated */ function wp_install( $blog_title, @@ -1865,7 +1867,7 @@ function upgrade_340() { if ( 'yes' === $wpdb->get_var( "SELECT autoload FROM $wpdb->options WHERE option_name = 'uninstall_plugins'" ) ) { $uninstall_plugins = get_option( 'uninstall_plugins' ); delete_option( 'uninstall_plugins' ); - add_option( 'uninstall_plugins', $uninstall_plugins, null, false ); + add_option( 'uninstall_plugins', $uninstall_plugins, '', false ); } } } 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-admin/update.php b/src/wp-admin/update.php index a6c59ec06dab2..a0ba9cfc069b2 100644 --- a/src/wp-admin/update.php +++ b/src/wp-admin/update.php @@ -87,7 +87,7 @@ wp_redirect( admin_url( 'update.php?action=activate-plugin&success=true&plugin=' . urlencode( $plugin ) . '&_wpnonce=' . $_GET['_wpnonce'] ) ); die(); } - iframe_header( __( 'Plugin Reactivation' ), true ); + iframe_header( __( 'Plugin Reactivation' ) ); if ( isset( $_GET['success'] ) ) { echo '

' . __( 'Plugin reactivated successfully.' ) . '

'; } 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/author-template.php b/src/wp-includes/author-template.php index b27bbf62379d4..014adad971ca5 100644 --- a/src/wp-includes/author-template.php +++ b/src/wp-includes/author-template.php @@ -20,6 +20,8 @@ * * @param string $deprecated Deprecated. * @return string The author's display name, empty string if unknown. + * + * @phpstan-param '' $deprecated */ function get_the_author( $deprecated = '' ) { global $authordata; @@ -57,6 +59,9 @@ function get_the_author( $deprecated = '' ) { * @param string $deprecated Deprecated. * @param bool $deprecated_echo Deprecated. Use get_the_author(). Echo the string or return it. * @return string The author's display name, from get_the_author(). + * + * @phpstan-param '' $deprecated + * @phpstan-param true $deprecated_echo */ function the_author( $deprecated = '', $deprecated_echo = true ) { if ( ! empty( $deprecated ) ) { @@ -362,6 +367,8 @@ function get_the_author_posts_link() { * @since 4.4.0 Converted into a wrapper for get_the_author_posts_link() * * @param string $deprecated Unused. + * + * @phpstan-param '' $deprecated */ function the_author_posts_link( $deprecated = '' ) { if ( ! empty( $deprecated ) ) { diff --git a/src/wp-includes/block-patterns.php b/src/wp-includes/block-patterns.php index 50ca1a426378d..3e50b349d242c 100644 --- a/src/wp-includes/block-patterns.php +++ b/src/wp-includes/block-patterns.php @@ -277,6 +277,8 @@ function wp_normalize_remote_block_pattern( $pattern ) { * @since 6.3.0 Add 'pattern-directory/core' to the pattern's 'source'. * * @param WP_Screen $deprecated Unused. Formerly the screen that the current request was triggered from. + * + * @phpstan-param null $deprecated */ function _load_remote_block_patterns( $deprecated = null ) { if ( ! empty( $deprecated ) ) { diff --git a/src/wp-includes/block-supports/elements.php b/src/wp-includes/block-supports/elements.php index d765a2c2b4b5a..b2bcc1dba9ade 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-supports/typography.php b/src/wp-includes/block-supports/typography.php index e99d0254fb43d..dda44c0e5293b 100644 --- a/src/wp-includes/block-supports/typography.php +++ b/src/wp-includes/block-supports/typography.php @@ -560,6 +560,8 @@ function wp_get_computed_fluid_typography_value( $args = array() ) { * @param bool|array $settings Optional Theme JSON settings array that overrides any global theme settings. * Default is false. * @return string|null Font-size value or null if a size is not passed in $preset. + * + * @phpstan-param array $settings */ diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 4dcba75c6d8af..7db660b74cde9 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -1476,6 +1476,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(); @@ -1696,6 +1698,8 @@ function get_template_hierarchy( $slug, $is_custom = false, $template_prefix = ' * prepared for inserting or updating the database. * @param WP_REST_Request $deprecated Deprecated. Not used. * @return stdClass|WP_Error The updated object representing a template or template part. + * + * @phpstan-param null $deprecated */ function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated = null ) { if ( null !== $deprecated ) { diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 487c2765ac249..95a73e50c77b8 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2682,6 +2682,8 @@ function _wp_apply_block_content_filters( $content, $context = '', &$seen_ids = * * @param string $content Content to test. * @return int The block format version is 1 if the content contains one or more blocks, 0 otherwise. + * + * @phpstan-return ($content is '' ? 0 : 0|1) */ function block_version( $content ) { return has_blocks( $content ) ? 1 : 0; @@ -3082,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( @@ -3182,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/bookmark.php b/src/wp-includes/bookmark.php index 9e44d781909ed..2aabc22f2429f 100644 --- a/src/wp-includes/bookmark.php +++ b/src/wp-includes/bookmark.php @@ -20,6 +20,13 @@ * respectively. Default OBJECT. * @param string $filter Optional. How to sanitize bookmark fields. Default 'raw'. * @return array|object|null Type returned depends on $output value. + * + * @phpstan-param 'OBJECT'|'ARRAY_A'|'ARRAY_N' $output + * @phpstan-return null|( + * $output is 'ARRAY_A' + * ? array + * : ($output is 'ARRAY_N' ? array : stdClass) + * ) */ function get_bookmark( $bookmark, $output = OBJECT, $filter = 'raw' ) { global $wpdb; @@ -74,6 +81,9 @@ function get_bookmark( $bookmark, $output = OBJECT, $filter = 'raw' ) { * @param int $bookmark The bookmark ID to get field. * @param string $context Optional. The context of how the field will be used. Default 'display'. * @return string|WP_Error + * + * @phpstan-param 'link_id'|'link_url'|'link_name'|'link_image'|'link_target'|'link_description'|'link_visible'|'link_owner'|'link_rating'|'link_updated'|'link_rel'|'link_notes'|'link_rss'|'link_category' $field + * @phpstan-return array>|int|string */ function get_bookmark_field( $field, $bookmark, $context = 'display' ) { $bookmark = (int) $bookmark; @@ -396,6 +406,9 @@ function sanitize_bookmark( $bookmark, $context = 'display' ) { * @param string $context How to filter the field value. Accepts 'raw', 'edit', 'db', * 'display', 'attribute', or 'js'. Default 'display'. * @return mixed The filtered value. + * + * @phpstan-param 'link_id'|'link_url'|'link_name'|'link_image'|'link_target'|'link_description'|'link_visible'|'link_owner'|'link_rating'|'link_updated'|'link_rel'|'link_notes'|'link_rss'|'link_category' $field + * @phpstan-return array|int|string */ function sanitize_bookmark_field( $field, $value, $bookmark_id, $context ) { $int_fields = array( 'link_id', 'link_rating' ); diff --git a/src/wp-includes/category-template.php b/src/wp-includes/category-template.php index f268f93cbc461..3f87eff239b2a 100644 --- a/src/wp-includes/category-template.php +++ b/src/wp-includes/category-template.php @@ -43,6 +43,8 @@ function get_category_link( $category ) { * @param bool $nicename Optional. Whether to use nice name for display. Default false. * @param array $deprecated Not used. * @return string|WP_Error A list of category parents on success, WP_Error on failure. + * + * @phpstan-param array{} $deprecated */ function get_category_parents( $category_id, $link = false, $separator = '/', $nicename = false, $deprecated = array() ) { @@ -534,6 +536,8 @@ function wp_dropdown_categories( $args = '' ) { * } * @return void|string|false Void if 'echo' argument is true, HTML list of categories if 'echo' is false. * False if the taxonomy does not exist. + * + * @phpstan-return ($args is array{echo: false|0, ...} ? string|false : false|void) */ function wp_list_categories( $args = '' ) { $defaults = array( @@ -849,6 +853,8 @@ function default_topic_count_scale( $count ) { * 0, 1, or their bool equivalents. * } * @return string|string[] Tag cloud as a string or an array, depending on 'format' argument. + * + * @phpstan-return ($args is array{format: 'array', ...} ? array : string) */ function wp_generate_tag_cloud( $tags, $args = '' ) { $defaults = array( diff --git a/src/wp-includes/category.php b/src/wp-includes/category.php index dbb48d630b076..eb9f83a2ed156 100644 --- a/src/wp-includes/category.php +++ b/src/wp-includes/category.php @@ -22,6 +22,24 @@ * @type string $taxonomy Taxonomy to retrieve terms for. Default 'category'. * } * @return array List of category objects. + * + * @phpstan-return ( + * $args is array{fields: 'count', ...} + * ? list + * : ( + * $args is array{fields: 'names'|'slugs', ...} + * ? list + * : ( + * $args is array{fields: 'id=>name'|'id=>slug', ...} + * ? array + * : ( + * $args is array{fields: 'id=>parent', ...} + * ? array + * : ($args is array{fields: 'ids'|'tt_ids', ...} ? list : array) + * ) + * ) + * ) + * ) */ function get_categories( $args = '' ) { $defaults = array( 'taxonomy' => 'category' ); @@ -88,6 +106,9 @@ function get_categories( $args = '' ) { * @return WP_Term|array|WP_Error|null Category data in type defined by $output parameter. * Returns a WP_Term object with backwards compatible property aliases filled in. * WP_Error if $category is empty, null if it does not exist. + * + * @phpstan-param 'OBJECT'|'ARRAY_A'|'ARRAY_N' $output + * @phpstan-return ($category is object ? array|WP_Term : array|WP_Term|WP_Error|null) & ($output is 'ARRAY_A' ? array|WP_Error|null : ($output is 'ARRAY_N' ? array|WP_Error|null : WP_Term|WP_Error|null)) */ function get_category( $category, $output = OBJECT, $filter = 'raw' ) { $category = get_term( $category, 'category', $output, $filter ); @@ -121,6 +142,13 @@ function get_category( $category, $output = OBJECT, $filter = 'raw' ) { * correspond to a WP_Term object, an associative array, or a numeric array, * respectively. Default OBJECT. * @return WP_Term|array|WP_Error|null Type is based on $output value. + * + * @phpstan-param 'OBJECT'|'ARRAY_A'|'ARRAY_N' $output + * @phpstan-return ( + * $output is 'ARRAY_A' + * ? array|WP_Error|null + * : ($output is 'ARRAY_N' ? array|WP_Error|null : WP_Term|WP_Error|null) + * ) */ function get_category_by_path( $category_path, $full_match = true, $output = OBJECT ) { $category_path = rawurlencode( urldecode( $category_path ) ); @@ -259,6 +287,10 @@ function cat_is_ancestor_of( $cat1, $cat2 ) { * @param object|array $category Category data. * @param string $context Optional. Default 'display'. * @return object|array Same type as $category with sanitized data for safe use. + * + * @phpstan-template T of array|object + * @phpstan-param T $category + * @phpstan-return T */ function sanitize_category( $category, $context = 'display' ) { return sanitize_term( $category, 'category', $context ); @@ -293,6 +325,20 @@ function sanitize_category_field( $field, $value, $cat_id, $context ) { * } * @return WP_Term[]|int|WP_Error Array of 'post_tag' term objects, a count thereof, * or WP_Error if any of the taxonomies do not exist. + * + * @phpstan-return ( + * $args is array{fields: 'names'|'slugs', ...} + * ? list + * : ( + * $args is array{fields: 'id=>name'|'id=>slug', ...} + * ? array + * : ( + * $args is array{fields: 'id=>parent', ...} + * ? array + * : ( $args is array{fields: 'ids'|'tt_ids', ...} ? list : array ) + * ) + * ) + * )|WP_Error */ function get_tags( $args = '' ) { $defaults = array( 'taxonomy' => 'post_tag' ); 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-block-list.php b/src/wp-includes/class-wp-block-list.php index b56b3bd69dc3c..fb21ef75a5536 100644 --- a/src/wp-includes/class-wp-block-list.php +++ b/src/wp-includes/class-wp-block-list.php @@ -10,6 +10,8 @@ * Class representing a list of block instances. * * @since 5.5.0 + * + * @phpstan-implements ArrayAccess */ #[AllowDynamicProperties] class WP_Block_List implements Iterator, ArrayAccess, Countable { @@ -69,6 +71,8 @@ public function __construct( $blocks, $available_context = array(), $registry = * * @param int $offset Offset of block to check for. * @return bool Whether block exists. + * + * @phpstan-param int $offset */ #[ReturnTypeWillChange] public function offsetExists( $offset ) { @@ -84,6 +88,9 @@ public function offsetExists( $offset ) { * * @param int $offset Offset of block value to retrieve. * @return WP_Block|null Block value if exists, or null. + * + * @phpstan-param int $offset + * @phpstan-return WP_Block|null */ #[ReturnTypeWillChange] public function offsetGet( $offset ) { @@ -107,6 +114,9 @@ public function offsetGet( $offset ) { * * @param int $offset Offset of block value to set. * @param array|WP_Block $value Block value. + * + * @phpstan-param int|null $offset + * @phpstan-return void */ #[ReturnTypeWillChange] public function offsetSet( $offset, $value ) { @@ -125,6 +135,9 @@ public function offsetSet( $offset, $value ) { * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php * * @param int $offset Offset of block value to unset. + * + * @phpstan-param int $offset + * @phpstan-return void */ #[ReturnTypeWillChange] public function offsetUnset( $offset ) { diff --git a/src/wp-includes/class-wp-block-supports.php b/src/wp-includes/class-wp-block-supports.php index cf2d84f3b6756..41cbe2b81c41a 100644 --- a/src/wp-includes/class-wp-block-supports.php +++ b/src/wp-includes/class-wp-block-supports.php @@ -197,6 +197,9 @@ 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 + * @phpstan-return ($extra_attributes is empty ? string : non-falsy-string) */ 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 c2daba389bd75..4791b3f7b3255 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(); @@ -472,6 +474,16 @@ protected function recurse_deps( $queue, $handle ) { * @param string $handle Name of the item. Should be unique. * @param string $status Optional. Status of the item to query. Default 'registered'. * @return bool|_WP_Dependency Found, or object Item data. + * + * @phpstan-return ( + * $handle is not non-empty-string + * ? false + * : ( + * $status is not 'registered'|'scripts'|'enqueued'|'queued'|'to_do'|'to_print'|'done'|'printed' + * ? false + * : ( $status is 'registered'|'scripts' ? _WP_Dependency|false : bool ) + * ) + * ) */ public function query( $handle, $status = 'registered' ) { switch ( $status ) { @@ -529,6 +541,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-http.php b/src/wp-includes/class-wp-http.php index 13b82d95bfbd2..20d61ebf98cfb 100644 --- a/src/wp-includes/class-wp-http.php +++ b/src/wp-includes/class-wp-http.php @@ -633,6 +633,15 @@ 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 array{ + * headers: \WpOrg\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() ) { $defaults = array( 'method' => 'POST' ); @@ -651,6 +660,15 @@ 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 array{ + * headers: \WpOrg\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() ) { $defaults = array( 'method' => 'GET' ); @@ -669,6 +687,15 @@ 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 array{ + * headers: \WpOrg\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() ) { $defaults = array( 'method' => 'HEAD' ); diff --git a/src/wp-includes/class-wp-locale.php b/src/wp-includes/class-wp-locale.php index e18c0c4f1d897..37a2abf5cec53 100644 --- a/src/wp-includes/class-wp-locale.php +++ b/src/wp-includes/class-wp-locale.php @@ -119,6 +119,8 @@ class WP_Locale { * * @since 6.2.0 * @var string + * + * @phpstan-var 'characters_excluding_spaces'|'characters_including_spaces'|'words' */ public $word_count_type; @@ -442,6 +444,8 @@ public function get_list_item_separator() { * * @return string Localized word count type. Possible values are `characters_excluding_spaces`, * `characters_including_spaces`, or `words`. Defaults to `words`. + * + * @phpstan-return 'characters_excluding_spaces'|'characters_including_spaces'|'words' */ public function get_word_count_type() { diff --git a/src/wp-includes/class-wp-object-cache.php b/src/wp-includes/class-wp-object-cache.php index cda63e66d49ef..d79246ad2e3b0 100644 --- a/src/wp-includes/class-wp-object-cache.php +++ b/src/wp-includes/class-wp-object-cache.php @@ -420,6 +420,8 @@ public function get_multiple( $keys, $group = 'default', $force = false ) { * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @param bool $deprecated Optional. Unused. Default false. * @return bool True on success, false if the contents were not deleted. + * + * @phpstan-param false $deprecated */ public function delete( $key, $group = 'default', $deprecated = false ) { if ( ! $this->is_valid_key( $key ) ) { diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 9385ae832ff66..7cf09cd4ca24d 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -14,6 +14,10 @@ * * @since 1.5.0 * @since 4.5.0 Removed the `$comments_popup` property. + * + * @phpstan-property-read bool $query_vars_changed + * @phpstan-property-read bool|string $query_vars_hash + * @phpstan-method void init_query_flags() */ #[AllowDynamicProperties] class WP_Query { diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index b6c205bba03bd..da7720d9e89d8 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -240,6 +240,8 @@ protected static function has_same_registered_blocks( $origin ) { * @type bool $with_supports Whether to include theme supports in the data. Default true. * } * @return WP_Theme_JSON Entity that holds theme data. + * + * @phpstan-param array{} $deprecated */ public static function get_theme_data( $deprecated = array(), $options = array() ) { if ( ! empty( $deprecated ) ) { diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php index 87399e399a198..2dfd6188f5ed3 100644 --- a/src/wp-includes/class-wp-theme.php +++ b/src/wp-includes/class-wp-theme.php @@ -5,6 +5,22 @@ * @package WordPress * @subpackage Theme * @since 3.4.0 + * + * @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' + * @phpstan-property-read string $name + * @phpstan-property-read string $title + * @phpstan-property-read string $version + * @phpstan-property-read string $parent_theme + * @phpstan-property-read string $template_dir + * @phpstan-property-read string $stylesheet_dir + * @phpstan-property-read string $template + * @phpstan-property-read string $stylesheet + * @phpstan-property-read string $screenshot + * @phpstan-property-read string $description + * @phpstan-property-read string $author + * @phpstan-property-read list $tags + * @phpstan-property-read string $theme_root + * @phpstan-property-read string $theme_root_uri */ #[AllowDynamicProperties] final class WP_Theme implements ArrayAccess { @@ -654,6 +670,8 @@ public function offsetUnset( $offset ) {} * * @param mixed $offset * @return bool + * + * @phpstan-return ($offset is ThemeKey ? true : false) */ #[ReturnTypeWillChange] public function offsetExists( $offset ) { @@ -696,6 +714,8 @@ public function offsetExists( $offset ) { * * @param mixed $offset * @return mixed + * + * @phpstan-return ($offset is ThemeKey ? mixed : null) */ #[ReturnTypeWillChange] public function offsetGet( $offset ) { diff --git a/src/wp-includes/class-wp-widget-factory.php b/src/wp-includes/class-wp-widget-factory.php index 1f6ed3a58919e..24d3e05de0550 100644 --- a/src/wp-includes/class-wp-widget-factory.php +++ b/src/wp-includes/class-wp-widget-factory.php @@ -35,6 +35,7 @@ class WP_Widget_Factory { * @since 2.8.0 * @var array * @phpstan-var array + * @phpstan-var array */ public $widgets = array(); @@ -69,11 +70,14 @@ public function WP_Widget_Factory() { * @since 7.1.1 The key for an instance is prefixed so that it is never cast to an integer. * * @param string|WP_Widget $widget Either the name of a `WP_Widget` subclass or an instance of a `WP_Widget` subclass. + * + * @phpstan-param class-string|WP_Widget $widget */ public function register( $widget ) { if ( $widget instanceof WP_Widget ) { $this->widgets[ self::INSTANCE_KEY_PREFIX . spl_object_id( $widget ) ] = $widget; } else { + // @phpstan-ignore arguments.count (Widget classes declare their own constructor, which takes no arguments.) $this->widgets[ $widget ] = new $widget(); } } @@ -87,6 +91,8 @@ public function register( $widget ) { * @since 7.1.1 The key for an instance is prefixed so that it is never cast to an integer. * * @param string|WP_Widget $widget Either the name of a `WP_Widget` subclass or an instance of a `WP_Widget` subclass. + * + * @phpstan-param class-string|WP_Widget $widget */ public function unregister( $widget ) { if ( $widget instanceof WP_Widget ) { diff --git a/src/wp-includes/class-wp-widget.php b/src/wp-includes/class-wp-widget.php index b131c50db3226..9ae4f908af073 100644 --- a/src/wp-includes/class-wp-widget.php +++ b/src/wp-includes/class-wp-widget.php @@ -16,6 +16,8 @@ * * @since 2.8.0 * @since 4.4.0 Moved to its own file from wp-includes/widgets.php + * + * @phpstan-template T of array = array */ #[AllowDynamicProperties] class WP_Widget { @@ -109,6 +111,23 @@ 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 T $instance + * @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: boolean, + * widget_id: string, + * widget_name: string, + * } $args */ public function widget( $args, $instance ) { die( 'function WP_Widget::widget() must be overridden in a subclass.' ); @@ -127,6 +146,9 @@ public function widget( $args, $instance ) { * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Settings to save or bool false to cancel saving. + * + * @phpstan-param T $new_instance + * @phpstan-param T $old_instance */ public function update( $new_instance, $old_instance ) { return $new_instance; @@ -140,6 +162,8 @@ public function update( $new_instance, $old_instance ) { * @param array $instance The settings for the particular instance of the widget. * @return string|void Default return is 'noform'. A subclass which echoes its own * form returns nothing. + * + * @phpstan-param T $instance */ public function form( $instance ) { echo '

' . __( 'There are no options for this widget.' ) . '

'; @@ -213,6 +237,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 +264,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 ); @@ -357,6 +385,8 @@ public function is_preview() { * * @type int $number Number increment used for multiples of the same widget. * } + * + * @final */ public function display_callback( $args, $widget_args = 1 ) { if ( is_numeric( $widget_args ) ) { @@ -408,6 +438,9 @@ public function display_callback( $args, $widget_args = 1 ) { * @global array $wp_registered_widgets * * @param int $deprecated Not used. + * + * @phpstan-param 1 $deprecated + * @final */ public function update_callback( $deprecated = 1 ) { global $wp_registered_widgets; @@ -500,6 +533,8 @@ public function update_callback( $deprecated = 1 ) { * @type int $number Number increment used for multiples of the same widget. * } * @return string|null + * + * @final */ public function form_callback( $widget_args = 1 ) { if ( is_numeric( $widget_args ) ) { diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php index 5cf9508f8fbdf..f1d4c5bf6beab 100644 --- a/src/wp-includes/class-wpdb.php +++ b/src/wp-includes/class-wpdb.php @@ -1454,6 +1454,8 @@ private function _escape_identifier_value( $identifier ) { * @param mixed ...$args Further variables to substitute into the query's placeholders * if being called with individual arguments. * @return string|null Sanitized query string, if there is a query to prepare. + * + * @phpstan-param literal-string $query */ public function prepare( $query, ...$args ) { if ( is_null( $query ) ) { @@ -3087,6 +3089,8 @@ public function get_var( $query = null, $x = 0, $y = 0 ) { * ) * : null * ) + * + * @phpstan-param int<0, max> $y */ public function get_row( $query = null, $output = OBJECT, $y = 0 ) { $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; diff --git a/src/wp-includes/comment-template.php b/src/wp-includes/comment-template.php index f7068362174e2..cc2608d4220e1 100644 --- a/src/wp-includes/comment-template.php +++ b/src/wp-includes/comment-template.php @@ -893,6 +893,9 @@ function get_comments_link( $post = 0 ) { * * @param string $deprecated Not Used. * @param string $deprecated_2 Not Used. + * + * @phpstan-param '' $deprecated + * @phpstan-param '' $deprecated_2 */ function comments_link( $deprecated = '', $deprecated_2 = '' ) { if ( ! empty( $deprecated ) ) { @@ -1279,6 +1282,8 @@ function trackback_url( $deprecated_echo = true ) { * @since 0.71 * * @param int|string $deprecated Not used (Was $timezone = 0). + * + * @phpstan-param '' $deprecated */ function trackback_rdf( $deprecated = '' ) { if ( ! empty( $deprecated ) ) { diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index cf7faaa7cb4c2..de738f08a4078 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1884,6 +1884,8 @@ function wp_unspam_comment( $comment_id ) { * * @param int|WP_Comment $comment_id Comment ID or WP_Comment object * @return string|false Status might be 'trash', 'approved', 'unapproved', 'spam'. False on failure. + * + * @phpstan-return 'approved'|'spam'|'trash'|'unapproved'|false */ function wp_get_comment_status( $comment_id ) { $comment = get_comment( $comment_id ); @@ -2791,6 +2793,8 @@ function wp_send_note_notification( WP_User $user, WP_Comment $comment, ?WP_Post * @param string $comment_status New comment status, either 'hold', 'approve', 'spam', or 'trash'. * @param bool $wp_error Whether to return a WP_Error object if there is a failure. Default false. * @return bool|WP_Error True on success, false or WP_Error on failure. + * + * @phpstan-return ($wp_error is false ? bool : true|WP_Error) */ function wp_set_comment_status( $comment_id, $comment_status, $wp_error = false ) { global $wpdb; @@ -2866,6 +2870,8 @@ function wp_set_comment_status( $comment_id, $comment_status, $wp_error = false * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false. * @return int|false|WP_Error The value 1 if the comment was updated, 0 if not updated. * False or a WP_Error object on failure. + * + * @phpstan-return ( $wp_error is false ? int|false : int|WP_Error ) */ function wp_update_comment( $commentarr, $wp_error = false ) { global $wpdb; @@ -3181,6 +3187,8 @@ function wp_update_comment_count_now( $post_id ) { * @param string $url URL to ping. * @param string $deprecated Not Used. * @return string|false String containing URI on success, false on failure. + * + * @phpstan-param '' $deprecated */ function discover_pingback_server_uri( $url, $deprecated = '' ) { if ( ! empty( $deprecated ) ) { diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php index 61fec7ee4d3fe..232a25835a543 100644 --- a/src/wp-includes/cron.php +++ b/src/wp-includes/cron.php @@ -44,6 +44,9 @@ * database performance issues. * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false. * @return bool|WP_Error True if event successfully scheduled. False or WP_Error on failure. + * + * @phpstan-param list $args + * @phpstan-return ($wp_error is false ? bool : true|WP_Error) */ function wp_schedule_single_event( $timestamp, $hook, $args = array(), $wp_error = false ) { // Make sure timestamp is a positive integer. @@ -248,6 +251,9 @@ function wp_schedule_single_event( $timestamp, $hook, $args = array(), $wp_error * database performance issues. * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false. * @return bool|WP_Error True if event successfully scheduled. False or WP_Error on failure. + * + * @phpstan-param list $args + * @phpstan-return ($wp_error is false ? bool : true|WP_Error) */ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array(), $wp_error = false ) { // Make sure timestamp is a positive integer. @@ -363,6 +369,9 @@ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array(), $wp * database performance issues. * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false. * @return bool|WP_Error True if event successfully rescheduled. False or WP_Error on failure. + * + * @phpstan-param list $args + * @phpstan-return ($wp_error is false ? bool : true|WP_Error) */ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $wp_error = false ) { // Make sure timestamp is a positive integer. @@ -485,6 +494,9 @@ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array(), $ * arguments do not match exactly, the event will not be found. Default empty array. * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false. * @return bool|WP_Error True if event successfully unscheduled. False or WP_Error on failure. + * + * @phpstan-param list $args + * @phpstan-return ($wp_error is false ? bool : true|WP_Error) */ function wp_unschedule_event( $timestamp, $hook, $args = array(), $wp_error = false ) { // Make sure timestamp is a positive integer. @@ -572,6 +584,9 @@ function wp_unschedule_event( $timestamp, $hook, $args = array(), $wp_error = fa * @return int|false|WP_Error On success an integer indicating number of events unscheduled (0 indicates no * events were registered with the hook and arguments combination), false or WP_Error * if unscheduling one or more events fail. + * + * @phpstan-param list $args + * @phpstan-return (int<0, max>|($wp_error is false ? false : WP_Error)) */ function wp_clear_scheduled_hook( $hook, $args = array(), $wp_error = false ) { /* @@ -677,6 +692,8 @@ function wp_clear_scheduled_hook( $hook, $args = array(), $wp_error = false ) { * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false. * @return int|false|WP_Error On success an integer indicating number of events unscheduled (0 indicates no * events were registered on the hook), false or WP_Error if unscheduling fails. + * + * @phpstan-return ($wp_error is false ? int<0, max>|false : int<0, max>|WP_Error) */ function wp_unschedule_hook( $hook, $wp_error = false ) { /** @@ -774,6 +791,8 @@ function wp_unschedule_hook( $hook, $wp_error = false ) { * @type array $args Array containing each separate argument to pass to the hook's callback function. * @type int $interval Optional. The interval time in seconds for the schedule. Only present for recurring events. * } + * + * @phpstan-param list $args */ function wp_get_scheduled_event( $hook, $args = array(), $timestamp = null ) { /** @@ -855,6 +874,8 @@ function wp_get_scheduled_event( $hook, $args = array(), $timestamp = null ) { * event, so they must match those used when originally scheduling the event. If the * arguments do not match exactly, the event will not be found. Default empty array. * @return int|false The Unix timestamp (UTC) of the next time the event will occur. False if the event doesn't exist. + * + * @phpstan-param list $args */ function wp_next_scheduled( $hook, $args = array() ) { $next_event = wp_get_scheduled_event( $hook, $args ); @@ -1183,6 +1204,8 @@ function wp_get_schedules() { * @param array $args Optional. Arguments passed to the event's callback function. * Default empty array. * @return string|false Schedule name on success, false if no schedule. + * + * @phpstan-param list $args */ function wp_get_schedule( $hook, $args = array() ) { $schedule = false; diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 3b78d1610fdad..3180ef2d21e46 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -2518,6 +2518,16 @@ function is_taxonomy( $taxonomy ) { * @param string $taxonomy The taxonomy name to use * @param int $parent ID of parent term under which to confine the exists search. * @return mixed Get the term ID or term object, if exists. + * + * @phpstan-return ( + * $term is 0 + * ? 0 + * : ( + * $term is '' + * ? null + * : ($taxonomy is '' ? string|null : array{term_id: string, term_taxonomy_id: string}|null) + * ) + * ) */ function is_term( $term, $taxonomy = '', $parent = 0 ) { _deprecated_function( __FUNCTION__, '3.0.0', 'term_exists()' ); @@ -6498,6 +6508,9 @@ function wp_print_auto_sizes_contain_css_fix() { * * @param string|array $gpc String or array of data to slash. * @return string|array Slashed `$gpc`. + * + * @phpstan-pure + * @phpstan-return ($gpc is string ? string : array) */ function addslashes_gpc( $gpc ) { _deprecated_function( __FUNCTION__, '7.0.0', 'wp_slash()' ); diff --git a/src/wp-includes/embed.php b/src/wp-includes/embed.php index e87cf4ec57989..6a7501027a978 100644 --- a/src/wp-includes/embed.php +++ b/src/wp-includes/embed.php @@ -676,7 +676,7 @@ function get_oembed_response_data_for_url( $url, $args ) { } if ( $site && get_current_blog_id() !== (int) $site->blog_id ) { - switch_to_blog( $site->blog_id ); + switch_to_blog( (int) $site->blog_id ); $switched_blog = true; } } diff --git a/src/wp-includes/feed.php b/src/wp-includes/feed.php index 453cc9063cf75..c282da6b60344 100644 --- a/src/wp-includes/feed.php +++ b/src/wp-includes/feed.php @@ -99,6 +99,8 @@ function get_default_feed() { * * @param string $deprecated Unused. * @return string The document title. + * + * @phpstan-param '–' $deprecated */ function get_wp_title_rss( $deprecated = '–' ) { if ( '–' !== $deprecated ) { @@ -125,6 +127,8 @@ function get_wp_title_rss( $deprecated = '–' ) { * @since 4.4.0 The optional `$sep` parameter was deprecated and renamed to `$deprecated`. * * @param string $deprecated Unused. + * + * @phpstan-param '–' $deprecated */ function wp_title_rss( $deprecated = '–' ) { if ( '–' !== $deprecated ) { @@ -584,6 +588,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 b9d551d59d51b..d90ce799e1946 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 ); @@ -622,6 +624,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 +2282,9 @@ 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-param 'display'|'save' $context + * @phpstan-return lowercase-string */ function sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) { $title = strip_tags( $title ); @@ -2412,6 +2419,10 @@ function sanitize_title_with_dashes( $title, $raw_title = '', $context = 'displa * * @param string $orderby Order by clause to be validated. * @return string|false Returns $orderby if valid, false otherwise. + * + * @phpstan-template T of string + * @phpstan-param T $orderby + * @phpstan-return (T is non-falsy-string ? T|false : false) */ function sanitize_sql_orderby( $orderby ) { if ( preg_match( '/^\s*(([a-z0-9_]+|`[a-z0-9_]+`)(\s+(ASC|DESC))?\s*(,\s*(?=[a-z0-9_`])|$))+$/i', $orderby ) || preg_match( '/^\s*RAND\(\s*\)\s*$/i', $orderby ) ) { @@ -2488,6 +2499,8 @@ function sanitize_locale_name( $locale_name ) { * @param string $content String of characters to be converted. * @param string $deprecated Not used. * @return string Converted string. + * + * @phpstan-param '' $deprecated */ function convert_chars( $content, $deprecated = '' ) { if ( ! empty( $deprecated ) ) { @@ -2788,6 +2801,17 @@ function format_to_edit( $content, $rich_text = false ) { * @param int $number Number to append zeros to if not greater than threshold. * @param int $threshold Digit places number needs to be to not have zeros added. * @return string Adds leading zeros to number if needed. + * + * @phpstan-param int<0, max> $threshold + * @phpstan-return ( + * $threshold is 0 + * ? lowercase-string&non-empty-string&numeric-string + * : ( + * $number is int<0, max> + * ? lowercase-string&non-empty-string&numeric-string + * : lowercase-string&non-empty-string + * ) + * ) */ function zeroise( $number, $threshold ) { return sprintf( '%0' . $threshold . 's', $number ); @@ -2800,6 +2824,8 @@ function zeroise( $number, $threshold ) { * * @param string $value Value to which backslashes will be added. * @return string String with backslashes inserted. + * + * @phpstan-pure */ function backslashit( $value ) { if ( isset( $value[0] ) && $value[0] >= '0' && $value[0] <= '9' ) { @@ -2821,6 +2847,9 @@ function backslashit( $value ) { * * @param string $value Value to which trailing slash will be added. * @return string String with trailing slash added. + * + * @phpstan-pure + * @phpstan-return non-falsy-string */ function trailingslashit( $value ) { return untrailingslashit( $value ) . '/'; @@ -2836,6 +2865,8 @@ function trailingslashit( $value ) { * * @param string $value Value from which trailing slashes will be removed. * @return string String without the trailing slashes. + * + * @phpstan-pure */ function untrailingslashit( $value ) { return rtrim( $value, '/\\' ); @@ -2884,6 +2915,10 @@ function stripslashes_from_strings_only( $value ) { * * @param mixed $value The array or string to be encoded. * @return mixed The encoded value. + * + * @phpstan-template T + * @phpstan-param T $value + * @phpstan-return T */ function urlencode_deep( $value ) { return map_deep( $value, 'urlencode' ); @@ -2896,6 +2931,10 @@ function urlencode_deep( $value ) { * * @param mixed $value The array or string to be encoded. * @return mixed The encoded value. + * + * @phpstan-template T + * @phpstan-param T $value + * @phpstan-return T */ function rawurlencode_deep( $value ) { return map_deep( $value, 'rawurlencode' ); @@ -2908,6 +2947,10 @@ function rawurlencode_deep( $value ) { * * @param mixed $value The array or string to be decoded. * @return mixed The decoded value. + * + * @phpstan-template T + * @phpstan-param T $value + * @phpstan-return T */ function urldecode_deep( $value ) { return map_deep( $value, 'urldecode' ); @@ -2948,6 +2991,8 @@ function urldecode_deep( $value ) { * @param string $email_address Email address. * @param int $hex_encoding Optional. Set to 1 to enable hex encoding. * @return string Converted email address. + * + * @phpstan-param 0|1 $hex_encoding */ function antispambot( $email_address, $hex_encoding = 0 ) { $obfuscated = ''; @@ -3609,6 +3654,8 @@ function convert_smilies( $text ) { * @param string $email Email address to verify. * @param bool $deprecated Deprecated. * @return string|false Valid email address on success, false on failure. + * + * @phpstan-param false $deprecated */ function is_email( $email, $deprecated = false ) { if ( ! empty( $deprecated ) ) { @@ -5866,6 +5913,8 @@ function sanitize_trackback_urls( $to_ping ) { * T is array ? array, ( value-of is string ? string : value-of )> : T * ) * ) + * + * @phpstan-pure */ function wp_slash( $value ) { if ( is_array( $value ) ) { diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 4c323812991d7..9af7df4b32831 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -31,6 +31,8 @@ * @param bool $translate Whether the return date should be translated. Default true. * @return string|int|false Integer if `$format` is 'U' or 'G', string otherwise. * False on failure. + * + * @phpstan-return ($format is 'G'|'U' ? int|false : string|false) */ function mysql2date( $format, $date, $translate = true ) { if ( empty( $date ) ) { @@ -74,6 +76,8 @@ function mysql2date( $format, $date, $translate = true ) { * or PHP date format string (e.g. 'Y-m-d'). * @param bool $gmt Optional. Whether to use GMT timezone. Default false. * @return int|string Integer if `$type` is 'timestamp' or 'U', string otherwise. + * + * @phpstan-return ($type is 'timestamp'|'U' ? int : string) */ function current_time( $type, $gmt = false ) { // Don't use non-GMT timestamp, unless you know the difference and really need to. @@ -465,6 +469,7 @@ function number_format_i18n( $number, $decimals = 0 ) { * @return string|false Number string on success, false on failure. * * @phpstan-param int|float|numeric-string $bytes + * @phpstan-return ( $bytes is int<0, max> ? string : string|false ) */ function size_format( $bytes, $decimals = 0 ) { if ( ! is_numeric( $bytes ) ) { @@ -632,6 +637,10 @@ function get_weekstartend( $mysqlstring, $start_of_week = '' ) { * * @param string|array|object $data Data that might be serialized. * @return mixed A scalar data. + * + * @phpstan-template T of mixed + * @phpstan-param T $data + * @phpstan-return (T is array|object|string ? string : T) */ function maybe_serialize( $data ) { if ( is_array( $data ) || is_object( $data ) ) { @@ -840,6 +849,8 @@ function xmlrpc_removepostdata( $content ) { * * @param string $content Content to extract URLs from. * @return string[] Array of URLs found in passed string. + * + * @phpstan-return ($content is empty ? array{} : list) */ function wp_extract_urls( $content ) { preg_match_all( @@ -993,6 +1004,8 @@ function do_enclose( $content, $post ) { * @param string $url URL to retrieve HTTP headers from. * @param bool $deprecated Not Used. * @return \WpOrg\Requests\Utility\CaseInsensitiveDictionary|false Headers on success, false on failure. + * + * @phpstan-param false $deprecated */ function wp_get_http_headers( $url, $deprecated = false ) { if ( ! empty( $deprecated ) ) { @@ -1022,6 +1035,8 @@ function wp_get_http_headers( $url, $deprecated = false ) { * @global string $previousday The day of the previous post in the loop. * * @return int 1 when new day, 0 if not a new day. + * + * @phpstan-return 0|1 */ function is_new_day() { global $currentday, $previousday; @@ -1600,6 +1615,9 @@ function get_num_queries() { * * @param string $yn Character string containing either 'y' (yes) or 'n' (no). * @return bool True if 'y', false on anything else. + * + * @phpstan-pure + * @phpstan-return ($yn is 'y' ? true : false) */ function bool_from_yn( $yn ) { return ( 'y' === strtolower( $yn ) ); @@ -1879,6 +1897,8 @@ function is_blog_installed() { * @param int|string $action Optional. Nonce action name. Default -1. * @param string $name Optional. Nonce name. Default '_wpnonce'. * @return string Escaped URL with nonce action added. + * + * @phpstan-param -1|string $action */ function wp_nonce_url( $actionurl, $action = -1, $name = '_wpnonce' ) { $actionurl = str_replace( '&', '&', $actionurl ); @@ -1910,6 +1930,8 @@ function wp_nonce_url( $actionurl, $action = -1, $name = '_wpnonce' ) { * @param bool $referer Optional. Whether to set the referer field for validation. Default true. * @param bool $display Optional. Whether to display or return hidden form field. Default true. * @return string Nonce field HTML markup. + * + * @phpstan-param -1|string $action */ function wp_nonce_field( $action = -1, $name = '_wpnonce', $referer = true, $display = true ) { $name = esc_attr( $name ); @@ -2131,6 +2153,9 @@ function wp_mkdir_p( $target ) { * * @param string $path File path. * @return bool True if path is absolute, false is not absolute. + * + * @phpstan-assert-if-true =non-falsy-string $path + * @phpstan-return ($path is non-falsy-string ? bool : false) */ function path_is_absolute( $path ) { /* @@ -2173,6 +2198,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 ) ) { @@ -2939,6 +2966,9 @@ 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 + * @phpstan-param null $deprecated */ function wp_upload_bits( $name, $deprecated, $bits, $time = null ) { if ( ! empty( $deprecated ) ) { @@ -4459,6 +4489,9 @@ 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-param int<1, max> $depth + * @phpstan-return non-empty-string|false */ function wp_json_encode( $value, $flags = 0, $depth = 512 ) { $json = json_encode( $value, $flags, $depth ); @@ -5416,6 +5449,8 @@ function _wp_to_kebab_case( $input_string ) { * @return bool Whether the variable is a list. * * @phpstan-assert-if-true array $data + * @phpstan-pure + * @phpstan-return ($data is array ? true : false) */ function wp_is_numeric_array( $data ): bool { if ( ! is_array( $data ) ) { @@ -6227,6 +6262,8 @@ function _doing_it_wrong( $function_name, $message, $version ) { * before passing to this function to avoid being stripped {@see wp_kses()}. * @param int $error_level Optional. The designated error type for this error. * Only works with E_USER family of constants. Default E_USER_NOTICE. + * + * @phpstan-param \E_USER_ERROR|\E_USER_WARNING|\E_USER_NOTICE|\E_USER_DEPRECATED $error_level */ function wp_trigger_error( $function_name, $message, $error_level = E_USER_NOTICE ) { /** @@ -6414,6 +6451,8 @@ function iis7_supports_permalinks() { * @param string $file File path. * @param string[] $allowed_files Optional. Array of allowed files. Default empty array. * @return int 0 means nothing is wrong, greater than 0 means something was wrong. + * + * @phpstan-return ($file is '' ? 0 : ($allowed_files is empty ? 0|1|2 : 0|1|2|3)) */ function validate_file( $file, $allowed_files = array() ) { if ( ! is_scalar( $file ) || '' === $file ) { @@ -7170,6 +7209,8 @@ function __return_false() { // phpcs:ignore WordPress.NamingConventions.ValidFun * @since 3.0.0 * * @return int 0. + * + * @phpstan-return 0 */ function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return 0; @@ -7183,6 +7224,8 @@ function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunc * @since 3.0.0 * * @return array Empty array. + * + * @phpstan-return array{} */ function __return_empty_array() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return array(); @@ -7211,6 +7254,8 @@ function __return_null() { // phpcs:ignore WordPress.NamingConventions.ValidFunc * @see __return_null() * * @return string Empty string. + * + * @phpstan-return '' */ function __return_empty_string() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return ''; @@ -7424,6 +7469,8 @@ function wp_allowed_protocols() { * the raw array returned. Default true. * @return string|array Either a string containing a reversed comma separated trace or an array * of individual calls. + * + * @phpstan-return ($pretty is true ? string : list) */ function wp_debug_backtrace_summary( $ignore_class = null, $skip_frames = 0, $pretty = true ) { static $truncate_paths; @@ -7556,6 +7603,8 @@ function _device_can_upload() { * * @param string $path The resource path or URL. * @return bool True if the path is a stream URL. + * + * @phpstan-assert-if-true =non-falsy-string $path */ function wp_is_stream( $path ) { $scheme_separator = strpos( $path, '://' ); @@ -7733,6 +7782,8 @@ function wp_auth_check( $response ) { * * @param string $tag An HTML tag name. Example: 'video'. * @return string Tag RegEx. + * + * @phpstan-return ( $tag is ''|'0' ? '' : non-falsy-string ) */ function get_tag_regex( $tag ) { if ( empty( $tag ) ) { @@ -8142,6 +8193,9 @@ function wp_raise_memory_limit( $context = 'admin' ) { * @since 7.0.0 Uses wp_rand if available. * * @return string UUID. + * + * @phpstan-impure + * @phpstan-return lowercase-string&non-falsy-string */ function wp_generate_uuid4() { static $backup_randomizer = false; @@ -8179,6 +8233,11 @@ function wp_generate_uuid4() { * @param int $version Specify which version of UUID to check against. Default is none, * to accept any UUID version. Otherwise, only version allowed is `4`. * @return bool The string is a valid UUID or false on failure. + * + * @phpstan-template TUuid of string + * @phpstan-param TUuid $uuid + * @phpstan-assert-if-true =TUuid&lowercase-string&non-falsy-string $uuid + * @phpstan-return ($version is 4|null ? bool : false) */ function wp_is_uuid( $uuid, $version = null ) { @@ -8211,6 +8270,9 @@ function wp_is_uuid( $uuid, $version = null ) { * * @param string $prefix Prefix for the returned ID. * @return string Unique ID. + * + * @phpstan-impure + * @phpstan-return ($prefix is ''|numeric-string ? numeric-string : string)&non-falsy-string&($prefix is lowercase-string ? lowercase-string : string) */ function wp_unique_id( $prefix = '' ) { static $id_counter = 0; @@ -8230,6 +8292,9 @@ function wp_unique_id( $prefix = '' ) { * * @param string $prefix Optional. Prefix for the returned ID. Default empty string. * @return string Incremental ID per prefix. + * + * @phpstan-impure + * @phpstan-return ($prefix is ''|numeric-string ? numeric-string : string)&non-falsy-string&($prefix is lowercase-string ? lowercase-string : string) */ function wp_unique_prefixed_id( $prefix = '' ) { static $id_counters = array(); @@ -8263,6 +8328,9 @@ function wp_unique_prefixed_id( $prefix = '' ) { * @param array $data The input array to generate an ID from. * @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 { if ( empty( $data ) ) { @@ -9160,6 +9228,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; @@ -9427,6 +9497,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/general-template.php b/src/wp-includes/general-template.php index 63a3aa67123b2..df763b77a2f3d 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -1901,6 +1901,8 @@ function single_term_title( $prefix = '', $display = true ) { * @param string $prefix Optional. What to display before the title. * @param bool $display Optional. Whether to display or retrieve title. Default true. * @return string|false|null False if there's no valid title for the month. Title when retrieving. + * + * @phpstan-return ( $display is true ? false|null : false|string ) */ function single_month_title( $prefix = '', $display = true ) { global $wp_locale; @@ -4913,6 +4915,12 @@ function language_attributes( $doctype = 'html' ) { * } * @return string|string[]|null String of page links or array of page links, depending on 'type' argument. * Null if total number of pages is less than 2. + * + * @phpstan-return ( + * $args is array{total: int, ...} + * ? null + * : ($args is array{type: 'array', ...} ? list : string) + * ) */ function paginate_links( $args = '' ) { global $wp_query, $wp_rewrite; diff --git a/src/wp-includes/http.php b/src/wp-includes/http.php index c2855a8d8d9c1..98b6cf174a367 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, + * 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() ) { $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, + * 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() ) { $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, + * 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() ) { $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, + * 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() ) { $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, + * 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() ) { $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, + * 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() ) { $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, + * 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() ) { $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, + * 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() ) { $http = _wp_http_get_object(); @@ -555,6 +627,10 @@ function send_origin_headers() { * * @param string $url Request URL. * @return string|false Returns false if the URL is not safe, or the original URL if it is safe. + * + * @phpstan-template TUrl of string + * @phpstan-param TUrl $url + * @phpstan-return (TUrl is numeric|'' ? false : TUrl|false) */ function wp_http_validate_url( $url ) { if ( ! is_string( $url ) || '' === $url || is_numeric( $url ) ) { diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 9394b75989912..ed04ad1878ad1 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2643,6 +2643,8 @@ function kses_init() { * @param string $css A string of CSS rules, decoded from an HTML `style` attribute. * @param string $deprecated Not used. * @return string Filtered string of CSS rules, needing HTML escaping before sending back to a `style` attribute. + * + * @phpstan-param '' $deprecated */ function safecss_filter_attr( $css, $deprecated = '' ) { if ( ! empty( $deprecated ) ) { diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 7b9d2652d41dd..95c41fb3f6bdc 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -995,6 +995,8 @@ function load_default_textdomain( $locale = null ) { * @param string|false $plugin_rel_path Optional. Relative path to WP_PLUGIN_DIR where the .mo file resides. * Default false. * @return bool True when textdomain is successfully loaded, false otherwise. + * + * @phpstan-param false $deprecated */ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) { /** @var WP_Textdomain_Registry $wp_textdomain_registry */ @@ -2066,6 +2068,8 @@ function wp_get_list_item_separator() { * * @return string Locale-specific word count type. Possible values are `characters_excluding_spaces`, * `characters_including_spaces`, or `words`. Defaults to `words`. + * + * @phpstan-return 'characters_excluding_spaces'|'characters_including_spaces'|'words' */ function wp_get_word_count_type() { global $wp_locale; diff --git a/src/wp-includes/l10n/class-wp-translations.php b/src/wp-includes/l10n/class-wp-translations.php index e919fea8b94b3..b158501b64e83 100644 --- a/src/wp-includes/l10n/class-wp-translations.php +++ b/src/wp-includes/l10n/class-wp-translations.php @@ -112,6 +112,11 @@ private function make_entry( $original, $translations ): Translation_Entry { * @param int|float $count Count. Should be an integer, but some plugins pass floats. * @param string|null $context Context. * @return string|null Translation if it exists, or the unchanged singular string. + * + * @phpstan-template T of string|null + * @phpstan-param T $singular + * @phpstan-param int $count + * @phpstan-return ($singular is null ? null : ($plural is null ? T : string)) */ public function translate_plural( $singular, $plural, $count = 1, $context = '' ) { if ( null === $singular || null === $plural ) { @@ -135,6 +140,8 @@ public function translate_plural( $singular, $plural, $count = 1, $context = '' * @param string|null $singular Singular string. * @param string|null $context Context. * @return string|null Translation if it exists, or the unchanged singular string + * + * @phpstan-return ($singular is null ? null : string) */ public function translate( $singular, $context = '' ) { if ( null === $singular ) { diff --git a/src/wp-includes/link-template.php b/src/wp-includes/link-template.php index 10bda681154f9..1dd869dfae709 100644 --- a/src/wp-includes/link-template.php +++ b/src/wp-includes/link-template.php @@ -153,6 +153,8 @@ function wp_force_plain_post_permalink( $post = null, $sample = null ) { * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`. * @param bool $leavename Optional. Whether to keep post name or page name. Default false. * @return string|false The permalink URL. False if the post does not exist. + * + * @phpstan-return ($post is WP_Post ? string : string|false) */ function get_the_permalink( $post = 0, $leavename = false ) { return get_permalink( $post, $leavename ); @@ -166,6 +168,8 @@ function get_the_permalink( $post = 0, $leavename = false ) { * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`. * @param bool $leavename Optional. Whether to keep post name or page name. Default false. * @return string|false The permalink URL. False if the post does not exist. + * + * @phpstan-return ($post is WP_Post ? string : string|false) */ function get_permalink( $post = 0, $leavename = false ) { $rewritecode = array( @@ -320,6 +324,8 @@ function get_permalink( $post = 0, $leavename = false ) { * @param bool $leavename Optional. Whether to keep post name. Default false. * @param bool $sample Optional. Is it a sample permalink. Default false. * @return string|false The post permalink URL. False if the post does not exist. + * + * @phpstan-return ($post is WP_Post ? string : string|false) */ function get_post_permalink( $post = 0, $leavename = false, $sample = false ) { global $wp_rewrite; @@ -1554,6 +1560,8 @@ function edit_post_link( $text = null, $before = '', $after = '', $post = 0, $cs * @param string $deprecated Not used. * @param bool $force_delete Optional. Whether to bypass Trash and force deletion. Default false. * @return string|null The delete post link URL for the given post. + * + * @phpstan-param '' $deprecated */ function get_delete_post_link( $post = 0, $deprecated = '', $force_delete = false ) { if ( ! empty( $deprecated ) ) { @@ -4881,6 +4889,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/load.php b/src/wp-includes/load.php index 9d407453424c5..1846b04573110 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -11,6 +11,8 @@ * @since 4.4.0 * * @return string The HTTP protocol. Default: HTTP/1.0. + * + * @phpstan-return 'HTTP/1.0'|'HTTP/1.1'|'HTTP/2'|'HTTP/2.0'|'HTTP/3' */ function wp_get_server_protocol() { $protocol = $_SERVER['SERVER_PROTOCOL'] ?? ''; @@ -1464,6 +1466,31 @@ function is_multisite() { * * @param mixed $maybeint Data you wish to have converted to a non-negative integer. * @return int A non-negative integer. + * + * @phpstan-template T of int + * @phpstan-param T|scalar|array|resource|null $maybeint + * @phpstan-pure + * @phpstan-return ( + * $maybeint is T&int<0, max> + * ? T + * : ( + * $maybeint is int + * ? int<1, max> + * : ( + * $maybeint is empty + * ? 0 + * : ( + * $maybeint is numeric-string + * ? int<0, max> + * : ( + * $maybeint is string + * ? 0 + * : ($maybeint is true|non-empty-array ? 1 : ($maybeint is bool ? 0|1 : int<0, max>)) + * ) + * ) + * ) + * ) + * ) */ function absint( $maybeint ) { return abs( (int) $maybeint ); @@ -1477,6 +1504,8 @@ function absint( $maybeint ) { * @global int $blog_id * * @return int Site ID. + * + * @phpstan-return int<0, max> */ function get_current_blog_id() { global $blog_id; @@ -1800,6 +1829,7 @@ function wp_doing_cron() { * @return bool Whether the variable is an instance of WP_Error. * * @phpstan-assert-if-true WP_Error $thing + * @phpstan-return ($thing is WP_Error ? true : false) */ function is_wp_error( $thing ) { $is_wp_error = ( $thing instanceof WP_Error ); diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 4d7caf59a62e4..00ce37323ce4d 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -1432,6 +1432,8 @@ function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype = * @return bool True if the meta key was successfully registered in the global array, false if not. * Registering a meta key with distinct sanitize and auth callbacks will fire those callbacks, * but will not add to the global registry. + * + * @phpstan-param null $deprecated */ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { global $wp_meta_keys; diff --git a/src/wp-includes/ms-blogs.php b/src/wp-includes/ms-blogs.php index f834ceb23a88e..5464f6334c6d8 100644 --- a/src/wp-includes/ms-blogs.php +++ b/src/wp-includes/ms-blogs.php @@ -457,6 +457,8 @@ function delete_blog_option( $id, $option ) { * @param mixed $value The option value. * @param mixed $deprecated Not used. * @return bool True if the value was updated, false otherwise. + * + * @phpstan-param null $deprecated */ function update_blog_option( $id, $option, $value, $deprecated = null ) { $id = (int) $id; @@ -497,6 +499,8 @@ function update_blog_option( $id, $option, $value, $deprecated = null ) { * @param int $new_blog_id The ID of the blog to switch to. Default: current blog. * @param bool $deprecated Not used. * @return true Always returns true. + * + * @phpstan-param null $deprecated */ function switch_to_blog( $new_blog_id, $deprecated = null ) { global $wpdb; @@ -820,6 +824,8 @@ function get_blog_status( $id, $pref ) { * Can be used for pagination. Default 0. * @param int $quantity Optional. The maximum number of blogs to retrieve. Default 40. * @return array The list of blogs. + * + * @phpstan-param '' $deprecated */ function get_last_updated( $deprecated = '', $start = 0, $quantity = 40 ) { global $wpdb; diff --git a/src/wp-includes/ms-functions.php b/src/wp-includes/ms-functions.php index 81dda98f3e312..46fe2537d8521 100644 --- a/src/wp-includes/ms-functions.php +++ b/src/wp-includes/ms-functions.php @@ -1478,6 +1478,8 @@ function wpmu_create_blog( $domain, $path, $title, $user_id, $options = array(), * @param WP_Site|int $blog_id The new site's object or ID. * @param string $deprecated Not used. * @return bool + * + * @phpstan-param '' $deprecated */ function newblog_notify_siteadmin( $blog_id, $deprecated = '' ) { if ( is_object( $blog_id ) ) { @@ -2088,6 +2090,8 @@ function check_upload_mimes( $mimes ) { * @global wpdb $wpdb WordPress database abstraction object. * * @param string $deprecated Not used. + * + * @phpstan-param '' $deprecated */ function update_posts_count( $deprecated = '' ) { global $wpdb; @@ -2142,6 +2146,8 @@ function wpmu_log_new_registrations( $blog_id, $user_id ) { * * @type string $0 The current site's domain. * } + * + * @phpstan-param '' $deprecated */ function redirect_this_site( $deprecated = '' ) { return array( get_network()->domain ); diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 6399dab72e881..02e227ee7abb4 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -441,6 +441,12 @@ function update_sitemeta_cache( $site_ids ) { * for information on accepted arguments. Default empty array. * @return WP_Site[]|int[]|int List of WP_Site objects, a list of site IDs when 'fields' is set to 'ids', * or the number of sites when 'count' is passed as a query var. + * + * @phpstan-return ( + * $args is array{count: true, ...} + * ? int + * : ($args is array{fields: 'ids', ...} ? array : array) + * ) */ function get_sites( $args = array() ) { $query = new WP_Site_Query(); 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/option.php b/src/wp-includes/option.php index d5c179c645af3..f32f518d35580 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -1065,6 +1065,8 @@ function update_option( $option, $value, $autoload = null ) { * to not autoload them, by using false. * Default is null, which means WordPress will determine the autoload value. * @return bool True if the option was added, false otherwise. + * + * @phpstan-param '' $deprecated */ function add_option( $option, $value = '', $deprecated = '', $autoload = null ) { global $wpdb; @@ -1427,6 +1429,8 @@ function delete_transient( $transient ) { * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return mixed Value of transient. + * + * @phpstan-impure */ function get_transient( $transient ) { @@ -1930,6 +1934,8 @@ function delete_all_user_settings() { * @param mixed $default_value Optional. Value to return if the option doesn't exist. Default false. * @param bool $deprecated Whether to use cache. Multisite only. Always set to true. * @return mixed Value set for the option. + * + * @phpstan-param true $deprecated */ function get_site_option( $option, $default_value = false, $deprecated = true ) { return get_network_option( null, $option, $default_value ); @@ -3119,6 +3125,8 @@ function register_setting( $option_group, $option_name, $args = array() ) { * @param string $option_group The settings group name used during registration. * @param string $option_name The name of the option to unregister. * @param callable $deprecated Optional. Deprecated. + * + * @phpstan-param '' $deprecated */ function unregister_setting( $option_group, $option_name, $deprecated = '' ) { global $new_allowed_options, $wp_registered_settings; diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index aa39c31d78ce5..ac99a9742117b 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -97,6 +97,8 @@ function get_userdata( $user_id ) { * @param string $field The field to retrieve the user with. id | ID | slug | email | login. * @param int|string $value A value for $field. A user ID, slug, email address, or login name. * @return WP_User|false WP_User object on success, false on failure. + * + * @phpstan-return ($field is 'id'|'ID' ? ($value is int ? false : WP_User|false) : WP_User|false) */ function get_user_by( $field, $value ) { $userdata = WP_User::get_data_by( $field, $value ); @@ -185,6 +187,8 @@ function cache_users( $user_ids ) { * @param string|string[] $attachments Optional. Paths to files to attach. * @param string|string[] $embeds Optional. Paths to files to embed. * @return bool Whether the email was sent successfully. + * + * @phpstan-impure */ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array(), $embeds = array() ) { // Compact the input, apply the filters, and extract them back out. @@ -2440,6 +2444,9 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) * * @param string|int $action Optional. The nonce action. Default -1. * @return float Float value rounded up to the next highest integer. + * + * @phpstan-param -1|string $action + * @phpstan-impure */ function wp_nonce_tick( $action = -1 ) { /** @@ -2470,6 +2477,9 @@ function wp_nonce_tick( $action = -1 ) { * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago, * 2 if the nonce is valid and generated between 12-24 hours ago. * False if the nonce is invalid. + * + * @phpstan-param -1|string $action + * @phpstan-return 1|2|false */ function wp_verify_nonce( $nonce, $action = -1 ) { $nonce = (string) $nonce; @@ -2533,6 +2543,9 @@ function wp_verify_nonce( $nonce, $action = -1 ) { * * @param string|int $action Scalar value to add context to the nonce. * @return string The token. + * + * @phpstan-param -1|string $action + * @phpstan-return lowercase-string&non-falsy-string */ function wp_create_nonce( $action = -1 ) { $user = wp_get_current_user(); @@ -2715,6 +2728,9 @@ 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-param 'auth'|'logged_in'|'nonce'|'secure_auth' $scheme + * @phpstan-return lowercase-string&non-falsy-string */ function wp_hash( $data, $scheme = 'auth', $algo = 'md5' ) { $salt = wp_salt( $scheme ); @@ -2960,6 +2976,8 @@ function wp_password_needs_rehash( $hash, $user_id = '' ) { * @param bool $extra_special_chars Optional. Whether to include other special characters. * Used when generating secret keys and salts. Default false. * @return string The random password. + * + * @phpstan-impure */ function wp_generate_password( $length = 12, $special_chars = true, $extra_special_chars = false ) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; @@ -3005,6 +3023,8 @@ function wp_generate_password( $length = 12, $special_chars = true, $extra_speci * @param int $max Optional. Upper limit for the generated number. * Accepts positive integers. Defaults to 4294967295. * @return int A random non-negative number between min and max. + * + * @phpstan-impure */ function wp_rand( $min = null, $max = null ) { global $rnd_value; diff --git a/src/wp-includes/plugin.php b/src/wp-includes/plugin.php index 38e88aa96bb00..10e40626265a7 100644 --- a/src/wp-includes/plugin.php +++ b/src/wp-includes/plugin.php @@ -171,6 +171,8 @@ function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) * @param mixed ...$args Optional. Additional parameters to pass to the callback functions. * @no-named-arguments * @return mixed The filtered value after all hooked functions are applied to it. + * + * @phpstan-param non-empty-string $hook_name */ function apply_filters( $hook_name, $value, ...$args ) { global $wp_filter, $wp_filters, $wp_current_filter; @@ -226,6 +228,8 @@ function apply_filters( $hook_name, $value, ...$args ) { * @param string $hook_name The name of the filter hook. * @param non-empty-list $args The arguments supplied to the functions hooked to `$hook_name`. * @return mixed The filtered value after all hooked functions are applied to it. + * + * @phpstan-param non-empty-string $hook_name */ function apply_filters_ref_array( $hook_name, $args ) { global $wp_filter, $wp_filters, $wp_current_filter; @@ -285,6 +289,7 @@ function apply_filters_ref_array( $hook_name, $args ) { * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. * @phpstan-param Maybe_Callable|false $callback + * @phpstan-return ($callback is false ? bool : false|int) */ function has_filter( $hook_name, $callback = false, $priority = false ) { global $wp_filter; @@ -369,6 +374,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; @@ -416,6 +423,8 @@ function doing_filter( $hook_name = null ) { * * @param string $hook_name The name of the filter hook. * @return int The number of times the filter hook has been applied. + * + * @phpstan-return int<0, max> */ function did_filter( $hook_name ) { global $wp_filters; @@ -488,6 +497,8 @@ function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) * @param mixed ...$arg Optional. Additional arguments which are passed on to the * functions hooked to the action. Default empty. * @no-named-arguments + * + * @phpstan-param non-empty-string $hook_name */ function do_action( $hook_name, ...$arg ) { global $wp_filter, $wp_actions, $wp_current_filter; @@ -543,6 +554,8 @@ function do_action( $hook_name, ...$arg ) { * * @param string $hook_name The name of the action to be executed. * @param list $args The arguments supplied to the functions hooked to `$hook_name`. + * + * @phpstan-param non-empty-string $hook_name */ function do_action_ref_array( $hook_name, $args ) { global $wp_filter, $wp_actions, $wp_current_filter; @@ -600,6 +613,7 @@ function do_action_ref_array( $hook_name, $args ) { * If `$callback` and `$priority` are both provided, a boolean is returned * for whether the specific function is registered at that priority. * @phpstan-param Maybe_Callable|false $callback + * @phpstan-return ($callback is false ? bool : false|int) */ function has_action( $hook_name, $callback = false, $priority = false ) { return has_filter( $hook_name, $callback, $priority ); @@ -650,6 +664,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(); @@ -688,6 +704,8 @@ function doing_action( $hook_name = null ) { * * @param string $hook_name The name of the action hook. * @return int The number of times the action hook has been fired. + * + * @phpstan-return int<0, max> */ function did_action( $hook_name ) { global $wp_actions; @@ -725,6 +743,8 @@ function did_action( $hook_name ) { * @param string $replacement Optional. The hook that should have been used. Default empty. * @param string $message Optional. A message regarding the change. Default empty. * @return mixed The filtered value after all hooked functions are applied to it. + * + * @phpstan-param non-empty-string $hook_name */ function apply_filters_deprecated( $hook_name, $args, $version, $replacement = '', $message = '' ) { if ( ! has_filter( $hook_name ) ) { @@ -752,6 +772,8 @@ function apply_filters_deprecated( $hook_name, $args, $version, $replacement = ' * @param string $version The version of WordPress that deprecated the hook. * @param string $replacement Optional. The hook that should have been used. Default empty. * @param string $message Optional. A message regarding the change. Default empty. + * + * @phpstan-param non-empty-string $hook_name */ function do_action_deprecated( $hook_name, $args, $version, $replacement = '', $message = '' ) { if ( ! has_action( $hook_name ) ) { @@ -882,6 +904,9 @@ function plugin_dir_url( $file ) { * * @param string $file The filename of the plugin including the path. * @param callable $callback The function hooked to the 'activate_PLUGIN' action. + * + * @phpstan-param callable(bool): void $callback + * @phpstan-return void */ function register_activation_hook( $file, $callback ) { $file = plugin_basename( $file ); @@ -905,6 +930,9 @@ function register_activation_hook( $file, $callback ) { * * @param string $file The filename of the plugin including the path. * @param callable $callback The function hooked to the 'deactivate_PLUGIN' action. + * + * @phpstan-param callable(bool): void $callback + * @phpstan-return void */ function register_deactivation_hook( $file, $callback ) { $file = plugin_basename( $file ); @@ -936,6 +964,8 @@ function register_deactivation_hook( $file, $callback ) { * @param string $file Plugin file. * @param callable $callback The callback to run when the hook is called. Must be * a static method or function. + * + * @phpstan-param callable(): void $callback */ function register_uninstall_hook( $file, $callback ) { if ( is_array( $callback ) && is_object( $callback[0] ) ) { diff --git a/src/wp-includes/post-template.php b/src/wp-includes/post-template.php index 86ea3eca58271..3fefee51600bf 100644 --- a/src/wp-includes/post-template.php +++ b/src/wp-includes/post-template.php @@ -1637,6 +1637,8 @@ function walk_page_dropdown_tree( ...$args ) { * @param bool $fullsize Optional. Whether to use full size. Default false. * @param bool $deprecated Deprecated. Not used. * @param bool $permalink Optional. Whether to include permalink. Default false. + * + * @phpstan-param false $deprecated */ function the_attachment_link( $post = 0, $fullsize = false, $deprecated = false, $permalink = false ) { if ( ! empty( $deprecated ) ) { @@ -2051,6 +2053,8 @@ function wp_post_revision_title_expanded( $revision, $link = true ) { * * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post. * @param string $type 'all' (default), 'revision' or 'autosave' + * + * @phpstan-param 'all'|'revision'|'autosave' $type */ function wp_list_post_revisions( $post = 0, $type = 'all' ) { $post = get_post( $post ); diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index aeea4e5bafac2..3190d38d8d68f 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/rest-api.php b/src/wp-includes/rest-api.php index c1462890213e5..9d6808cd57010 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -30,6 +30,9 @@ * @param bool $override Optional. If the route already exists, should we override it? True overrides, * false merges (with newer overriding if duplicate keys exist). Default false. * @return bool True on success, false on error. + * + * @phpstan-param non-falsy-string $route_namespace + * @phpstan-param non-falsy-string $route */ function register_rest_route( $route_namespace, $route, $args = array(), $override = false ) { if ( empty( $route_namespace ) ) { @@ -694,6 +697,8 @@ function rest_ensure_request( $request ) { * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response * is already an instance, WP_REST_Response, otherwise * returns a new WP_REST_Response instance. + * + * @phpstan-return ($response is WP_Error ? WP_Error : WP_REST_Response) */ function rest_ensure_response( $response ) { if ( is_wp_error( $response ) ) { @@ -1434,6 +1439,8 @@ function rest_get_date_with_gmt( $date, $is_utc = false ) { * @since 4.7.0 * * @return int 401 if the user is not logged in, 403 if the user is logged in. + * + * @phpstan-return 401|403 */ function rest_authorization_required_code() { return is_user_logged_in() ? 403 : 401; @@ -1531,6 +1538,11 @@ function rest_is_ip_address( $ip ) { * * @param bool|string|int $value The value being evaluated. * @return bool Returns the proper associated boolean value. + * + * @phpstan-template T of bool|string|int + * @phpstan-param T $value + * @phpstan-pure + * @phpstan-return (T is bool ? T : (T is ''|'false'|'FALSE'|'0'|0 ? false : true)) */ function rest_sanitize_boolean( $value ) { // String values are translated to `true`; make sure 'false' is false. diff --git a/src/wp-includes/revision.php b/src/wp-includes/revision.php index 6e27fad4fa0a4..499fcc38080d4 100644 --- a/src/wp-includes/revision.php +++ b/src/wp-includes/revision.php @@ -18,6 +18,8 @@ * for insertion as a post revision. Default empty array. * @param bool $deprecated Not used. * @return string[] Array of fields that can be versioned. + * + * @phpstan-param false $deprecated */ function _wp_post_revision_fields( $post = array(), $deprecated = false ) { static $fields = null; @@ -307,6 +309,12 @@ function wp_get_post_autosave( $post_id, $user_id = 0 ) { * * @param int|WP_Post $post Post ID or post object. * @return int|false ID of revision's parent on success, false if not a revision. + * + * @phpstan-return ( + * $post is WP_Post + * ? false|int<0, max> + * : ($post is int ? false : false|int<0, max>) + * ) */ function wp_is_post_revision( $post ) { $post = wp_get_post_revision( $post ); diff --git a/src/wp-includes/rewrite.php b/src/wp-includes/rewrite.php index 976d2a014b9db..0ac4b08f3ffc0 100644 --- a/src/wp-includes/rewrite.php +++ b/src/wp-includes/rewrite.php @@ -247,6 +247,9 @@ 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-param callable(bool, string): void $callback + * @phpstan-return non-falsy-string */ function add_feed( $feedname, $callback ) { global $wp_rewrite; 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 7d5ba24e5617d..a8d8afe171b39 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -685,6 +685,8 @@ function wp_default_packages( $scripts ) { * * @param string $type The type of suffix to retrieve. * @return string The script suffix. + * + * @phpstan-return ''|'.min' */ function wp_scripts_get_suffix( $type = '' ) { static $suffixes; @@ -2938,6 +2940,9 @@ function wp_enqueue_editor_format_library_assets() { * * @param array $attributes Key-value pairs representing `