From 5c59eff60725fd9b625b7b5298c5144e1b4633f2 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 1 Oct 2026 08:53:28 +0000 Subject: [PATCH 1/3] Docs: Add class-level PHPStan annotations. Documents the magic properties of `WP_Theme` and `WP_Query` with `@phpstan-property-read`, along with `WP_Query::init_query_flags()` with `@phpstan-method`, and the element type of `WP_Block_List` with `@phpstan-implements ArrayAccess`. The `WP_Theme` properties that come from `WP_Theme::get()`, `::display()` or `::get_screenshot()` include `false`, as those methods do. Marks `WP_Widget::update_callback()` and `::form_callback()` as `@final`, as their documentation says they are not to be overridden. See #65817. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VieGtSQtUoVfHiGwTmkvtc --- src/wp-includes/class-wp-block-list.php | 11 +++++++ src/wp-includes/class-wp-query.php | 4 +++ src/wp-includes/class-wp-theme.php | 14 +++++++++ src/wp-includes/class-wp-widget.php | 4 +++ .../phpstan/baselines/property.notFound.neon | 30 ------------------- tests/phpstan/baselines/property.private.neon | 10 ------- 6 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/wp-includes/class-wp-block-list.php b/src/wp-includes/class-wp-block-list.php index b56b3bd69dc3c..b730d07e93af0 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,8 @@ 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 */ #[ReturnTypeWillChange] public function offsetSet( $offset, $value ) { @@ -125,6 +134,8 @@ 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 */ #[ReturnTypeWillChange] public function offsetUnset( $offset ) { diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 513f0f702c61f..0fc2276bbc3de 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.php b/src/wp-includes/class-wp-theme.php index 87fcd7eec72b4..3e6770e6b5bc3 100644 --- a/src/wp-includes/class-wp-theme.php +++ b/src/wp-includes/class-wp-theme.php @@ -7,6 +7,20 @@ * @since 3.4.0 * * @phpstan-type Theme_Key '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|false $name + * @phpstan-property-read string|false $title + * @phpstan-property-read string|false $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|false $screenshot + * @phpstan-property-read string|false $description + * @phpstan-property-read string|false $author + * @phpstan-property-read string[]|false $tags + * @phpstan-property-read string $theme_root + * @phpstan-property-read string $theme_root_uri */ #[AllowDynamicProperties] final class WP_Theme implements ArrayAccess { diff --git a/src/wp-includes/class-wp-widget.php b/src/wp-includes/class-wp-widget.php index b131c50db3226..4e1edd72fd22b 100644 --- a/src/wp-includes/class-wp-widget.php +++ b/src/wp-includes/class-wp-widget.php @@ -408,6 +408,8 @@ public function display_callback( $args, $widget_args = 1 ) { * @global array $wp_registered_widgets * * @param int $deprecated Not used. + * + * @final */ public function update_callback( $deprecated = 1 ) { global $wp_registered_widgets; @@ -500,6 +502,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/tests/phpstan/baselines/property.notFound.neon b/tests/phpstan/baselines/property.notFound.neon index 460a5082ab0b8..521bc7619766a 100644 --- a/tests/phpstan/baselines/property.notFound.neon +++ b/tests/phpstan/baselines/property.notFound.neon @@ -158,26 +158,6 @@ parameters: identifier: property.notFound count: 1 path: ../../../src/wp-admin/includes/class-walker-nav-menu-edit.php - - - message: '#^Access to an undefined property WP_Theme\:\:\$author\.$#' - identifier: property.notFound - count: 3 - path: ../../../src/wp-admin/includes/class-wp-debug-data.php - - - message: '#^Access to an undefined property WP_Theme\:\:\$name\.$#' - identifier: property.notFound - count: 4 - path: ../../../src/wp-admin/includes/class-wp-debug-data.php - - - message: '#^Access to an undefined property WP_Theme\:\:\$parent_theme\.$#' - identifier: property.notFound - count: 1 - path: ../../../src/wp-admin/includes/class-wp-debug-data.php - - - message: '#^Access to an undefined property WP_Theme\:\:\$version\.$#' - identifier: property.notFound - count: 6 - path: ../../../src/wp-admin/includes/class-wp-debug-data.php - message: '#^Access to an undefined property WP_Theme\:\:\$auto_update_forced\.$#' identifier: property.notFound @@ -188,11 +168,6 @@ parameters: identifier: property.notFound count: 1 path: ../../../src/wp-admin/includes/class-wp-ms-themes-list-table.php - - - message: '#^Access to an undefined property WP_Theme\:\:\$name\.$#' - identifier: property.notFound - count: 8 - path: ../../../src/wp-admin/includes/class-wp-site-health.php - message: '#^Access to an undefined property WP_Post\:\:\$_wp_attachment_image_alt\.$#' identifier: property.notFound @@ -228,11 +203,6 @@ parameters: identifier: property.notFound count: 2 path: ../../../src/wp-admin/nav-menus.php - - - message: '#^Access to an undefined property WP_Theme\:\:\$version\.$#' - identifier: property.notFound - count: 1 - path: ../../../src/wp-admin/update-core.php - message: '#^Access to an undefined property WP_Post\:\:\$description\.$#' identifier: property.notFound diff --git a/tests/phpstan/baselines/property.private.neon b/tests/phpstan/baselines/property.private.neon index 900a8e51dc423..7549ab9c5ddd5 100644 --- a/tests/phpstan/baselines/property.private.neon +++ b/tests/phpstan/baselines/property.private.neon @@ -18,16 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Access to private property WP_Theme\:\:\$stylesheet\.$#' - identifier: property.private - count: 20 - path: ../../../src/wp-admin/includes/class-wp-debug-data.php - - - message: '#^Access to private property WP_Theme\:\:\$template\.$#' - identifier: property.private - count: 2 - path: ../../../src/wp-admin/includes/class-wp-debug-data.php - message: '#^Access to private property WP_Block_Type\:\:\$uses_context\.$#' identifier: property.private From 485d70782a667eec9357e1f0072e98d634fac492 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 15:58:48 +0000 Subject: [PATCH 2/3] Docs: Address review feedback on class annotations. Type WP_Theme::$parent_theme as string|false, since it comes from the parent theme's Name header, the same source as $name. Drop the @phpstan tags in WP_Block_List that repeat the canonical tags, and document the null offset of offsetSet() in the canonical @param instead. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015BuoHZQvJMvPEYp4Sfjgvd --- src/wp-includes/class-wp-block-list.php | 11 +---------- src/wp-includes/class-wp-theme.php | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/class-wp-block-list.php b/src/wp-includes/class-wp-block-list.php index b730d07e93af0..44e3055253a7b 100644 --- a/src/wp-includes/class-wp-block-list.php +++ b/src/wp-includes/class-wp-block-list.php @@ -71,8 +71,6 @@ 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 ) { @@ -88,9 +86,6 @@ 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 ) { @@ -112,10 +107,8 @@ public function offsetGet( $offset ) { * * @link https://www.php.net/manual/en/arrayaccess.offsetset.php * - * @param int $offset Offset of block value to set. + * @param int|null $offset Offset of block value to set, or null to append. * @param array|WP_Block $value Block value. - * - * @phpstan-param int|null $offset */ #[ReturnTypeWillChange] public function offsetSet( $offset, $value ) { @@ -134,8 +127,6 @@ 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 */ #[ReturnTypeWillChange] public function offsetUnset( $offset ) { diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php index 3e6770e6b5bc3..f975a41df7412 100644 --- a/src/wp-includes/class-wp-theme.php +++ b/src/wp-includes/class-wp-theme.php @@ -10,7 +10,7 @@ * @phpstan-property-read string|false $name * @phpstan-property-read string|false $title * @phpstan-property-read string|false $version - * @phpstan-property-read string $parent_theme + * @phpstan-property-read string|false $parent_theme * @phpstan-property-read string $template_dir * @phpstan-property-read string $stylesheet_dir * @phpstan-property-read string $template From 6120e0ec2cdff57e65078423357c08e4ed9ed480 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 07:29:23 +0000 Subject: [PATCH 3/3] Docs: Address further review feedback on class annotations. Use `@property-read` for the `WP_Theme` magic properties, as `WP_Comment` does, and align the property names. Type `WP_Query::$query_vars_hash` as `false|(lowercase-string&non-falsy-string)`. See #65817. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01T3XYNUvB9sQNDXA14uJQeH --- src/wp-includes/class-wp-query.php | 5 +++-- src/wp-includes/class-wp-theme.php | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 0fc2276bbc3de..58c4d10f3163e 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -15,8 +15,8 @@ * @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-property-read bool $query_vars_changed + * @phpstan-property-read false|(lowercase-string&non-falsy-string) $query_vars_hash * @phpstan-method void init_query_flags() */ #[AllowDynamicProperties] @@ -445,6 +445,7 @@ class WP_Query { * * @since 3.1.0 * @var bool|string + * @phpstan-var false|(lowercase-string&non-falsy-string) */ private $query_vars_hash = false; diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php index f975a41df7412..f7d4aba792a00 100644 --- a/src/wp-includes/class-wp-theme.php +++ b/src/wp-includes/class-wp-theme.php @@ -7,20 +7,20 @@ * @since 3.4.0 * * @phpstan-type Theme_Key '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|false $name - * @phpstan-property-read string|false $title - * @phpstan-property-read string|false $version - * @phpstan-property-read string|false $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|false $screenshot - * @phpstan-property-read string|false $description - * @phpstan-property-read string|false $author - * @phpstan-property-read string[]|false $tags - * @phpstan-property-read string $theme_root - * @phpstan-property-read string $theme_root_uri + * @property-read string|false $name + * @property-read string|false $title + * @property-read string|false $version + * @property-read string|false $parent_theme + * @property-read string $template_dir + * @property-read string $stylesheet_dir + * @property-read string $template + * @property-read string $stylesheet + * @property-read string|false $screenshot + * @property-read string|false $description + * @property-read string|false $author + * @property-read string[]|false $tags + * @property-read string $theme_root + * @property-read string $theme_root_uri */ #[AllowDynamicProperties] final class WP_Theme implements ArrayAccess {