Repository navigation
Code Quality: Define array shapes for block type selectors and viewport media queries - #15644
westonruter wants to merge 8 commits into
Conversation
`WP_Block_Type::$selectors` was documented as a plain `array`. Its shape follows the `selectors` schema in `block.json`: an optional `root` selector string, plus selectors for block support features (such as `color` or `typography`), each either a selector string or an array of selectors keyed by subfeature (such as `root` or `text`). Any feature key is allowed, since third-party blocks and the `states` and `filter` keys are not covered by the schema's listed properties.
Document this as `array<string, string|array<string, string>>`, with the more specific `array{ root?: string, ...<string, string|array<string, string>> }` in `@phpstan-` tags, on the property, on the `selectors` argument to the constructor, on the return value of `WP_Theme_JSON::get_block_selectors()`, on the `$block_selectors` parameter of `wp_get_state_style_groups()`, and on the `selectors` key of the `$metadata` parameter of `WP_Theme_JSON::get_feature_declarations_for_node()`. Also document the `$block_type` parameter of `WP_Theme_JSON::get_block_selectors()` as `WP_Block_Type` rather than `object`, since it comes from the block type registry and is passed to `wp_get_block_css_selector()`.
In `WP_Theme_JSON::get_block_selectors()`, the default selectors are now built by prepending the `root` selector to the feature selectors, rather than adding the feature selectors after it. The keys and their order are unchanged, but a feature named `root` in a subclass's `BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS` can no longer replace the root selector string with an array.
This resolves 9 PHPStan errors, including `missingType.iterableValue` on the property and the two functions, `return.type` for `mixed` returned from `wp_get_block_css_selector()` and `WP_Theme_JSON::get_block_selectors()`, and `argument.type` for `mixed` passed to `wp_add_state_style_group()`.
Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com>
Claude-Code-Session-Id: 7a12dabb-d901-4983-86e2-a9cbfedc1158
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
…()`. The `$state_style` parameter is a theme.json style object, keyed by style feature (such as `color` or `typography`), and each key is looked up in `$block_selectors`. Its values come from theme.json and are checked by the function itself, so they stay `mixed`. This resolves the `missingType.iterableValue` PHPStan error on the parameter. The caller, `wp_get_block_state_style_rules()`, narrows each state style from `mixed` only with `is_array()`, so it now reports an `argument.type` error for passing `array<mixed, mixed>`, which is a more accurate account of the untyped theme.json data. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 7a12dabb-d901-4983-86e2-a9cbfedc1158
`WP_Theme_JSON::get_viewport_media_queries()` only ever returns the `@mobile`, `@tablet`, and `@desktop` keys, each holding a non-empty `@media` query string, so document that in a `@phpstan-return` array shape. Without it, callers see each media query as `mixed`. The Navigation block passes them to `wp_style_engine_get_stylesheet_from_css_rules()` as the `rules_group`, which expects a string. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 32212707-a7f1-4bca-bef2-7f3b4982950b
…into fix/block-type-selectors-shape
This reverts commit aed1887. The guard was only needed because Core's `WP_Theme_JSON::get_viewport_media_queries()` documented a plain `array` return. That method now has an array shape in WordPress/wordpress-develop#15644, so with stubs generated from it, each media query is already a `non-falsy-string` and `rules_group` is narrowed without the check. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 32212707-a7f1-4bca-bef2-7f3b4982950b
…p#15644 That pull request adds the array shape for `WP_Theme_JSON::get_viewport_media_queries()`, which the Navigation block falls back to, so that its media queries are known to be strings when passed to the style engine as a `rules_group`. Regenerate the stubs with `--commit=trunk` once it is committed to Core, before this is merged. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 32212707-a7f1-4bca-bef2-7f3b4982950b
The `WP_Block_Type::$selectors` description said that every key other than `root` is a block support feature, but the `states` key instead holds the selectors for the block's custom states, keyed by state name (such as `-current`). Also realign the `@type` rows of the `$args` hash in the constructor's docblock, which the longer type of the `selectors` argument had pushed out of line. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 7a12dabb-d901-4983-86e2-a9cbfedc1158
…ctors()`. Add tests that: * The default selectors for a block without custom selectors list the root selector first, followed by the feature selectors in the order of `BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS`. * Custom selectors set by the block are returned as they are. * A feature named `root`, in a subclass that overrides `BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS`, does not replace the root selector string. Building the default selectors with `$feature_selectors['root'] ??= $root_selector` instead fails the first and third tests. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 7a12dabb-d901-4983-86e2-a9cbfedc1158
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. Props Bot commandsYou can trigger Props Bot actions by commenting on this PR:
|
There was a problem hiding this comment.
🟢 Approval recommended
No active issues were identified, and the change is narrowly scoped to typed selector metadata, root-selector preservation, and focused test coverage.
No open issues 🎉
What changed in this PR
This pull request adds precise PHPDoc/PHPStan array shapes for block selectors and viewport media queries, while preserving the root selector during default selector generation. It also adds focused PHPUnit coverage.
Changes:
- Documents selector shapes across block types, theme JSON processing, and state-style helpers.
- Documents optional
@mobile,@tablet, and@desktopmedia-query keys as non-empty strings. - Refactors default selector construction to preserve the root selector.
- Adds tests for selector ordering, custom selectors, and subclass root-selector handling.
| File | Description | Review note |
|---|---|---|
tests/phpunit/tests/theme/wpThemeJson.php |
Tests default selector ordering, custom selector passthrough, and root-selector preservation. | — |
src/wp-includes/class-wp-theme-json.php |
Adds selector and viewport media-query types and preserves the root selector during default generation. | Nit (1 vote): Keep the metadata shape open while narrowing the selectors key, since call sites provide additional keys such as path, selector, elements, and variations. |
src/wp-includes/class-wp-block-type.php |
Documents the structure of block selector metadata on the property and constructor arguments. | — |
src/wp-includes/block-supports/states.php |
Adds precise types for state styles and block selector metadata. | — |
Review effort: Lite
… helper. The helper calls `get_block_selectors()` on `WP_Theme_JSON` or a subclass of it, so document `$class_name` as `class-string<WP_Theme_JSON>` rather than `string`. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 7a12dabb-d901-4983-86e2-a9cbfedc1158
…tadata cache. The test for a feature named `root` creates an anonymous subclass of `WP_Theme_JSON` to get its class name. Creating it ran the `WP_Theme_JSON` constructor, which calls `static::get_blocks_metadata()` and fills the blocks metadata cache that the subclass shares with `WP_Theme_JSON`. That cached metadata used the subclass's feature level selectors, where `color` maps to `root`, for every registered block not already cached. As a result, later tests in the same process could have seen core blocks without their color feature selectors, depending on test order. Give the subclass an empty constructor, so that creating it leaves the cache alone. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 7a12dabb-d901-4983-86e2-a9cbfedc1158
The `WP_Block_Type::$selectors` property was documented only as an `array`, so PHPStan saw every selector read from it as `mixed`. Its shape now follows the `selectors` schema in `block.json`: an optional `root` selector string, plus the selectors for each block support feature, each either a string or an array of selectors keyed by subfeature. The shape stays open to other keys, since core blocks also use `states` and `filter`, and third-party blocks can add their own. It is applied wherever the selectors pass through `WP_Theme_JSON` and the block state styles. Similarly, `WP_Theme_JSON::get_viewport_media_queries()` only ever returns the `@mobile`, `@tablet`, and `@desktop` keys, each holding a media query string, so its return value is now documented with that shape. Callers such as the Navigation block, which passes each query to `wp_style_engine_get_stylesheet_from_css_rules()` as the `rules_group`, no longer see them as `mixed`. Developed in #15644. Follow-up to r55673, r56058, r62453, r62671, r64727. See #65817. git-svn-id: https://develop.svn.wordpress.org/trunk@65318 602fd350-edb4-49c9-b593-d223f7449a82
The `WP_Block_Type::$selectors` property was documented only as an `array`, so PHPStan saw every selector read from it as `mixed`. Its shape now follows the `selectors` schema in `block.json`: an optional `root` selector string, plus the selectors for each block support feature, each either a string or an array of selectors keyed by subfeature. The shape stays open to other keys, since core blocks also use `states` and `filter`, and third-party blocks can add their own. It is applied wherever the selectors pass through `WP_Theme_JSON` and the block state styles. Similarly, `WP_Theme_JSON::get_viewport_media_queries()` only ever returns the `@mobile`, `@tablet`, and `@desktop` keys, each holding a media query string, so its return value is now documented with that shape. Callers such as the Navigation block, which passes each query to `wp_style_engine_get_stylesheet_from_css_rules()` as the `rules_group`, no longer see them as `mixed`. Developed in WordPress/wordpress-develop#15644. Follow-up to r55673, r56058, r62453, r62671, r64727. See #65817. Built from https://develop.svn.wordpress.org/trunk@65318 git-svn-id: http://core.svn.wordpress.org/trunk@64458 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Resolve the conflicts in the generated files by generating them again. The WordPress stubs are generated from wordpress-develop trunk at r65320, which includes the shapes from WordPress/wordpress-develop#15644, and the PHPStan baseline is regenerated with them. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> Claude-Code-Session-Id: 32212707-a7f1-4bca-bef2-7f3b4982950b
* PHPStan: Resolve always-true type checks - Drop the `method_exists()` check for `WP_Interactivity_API::add_client_navigation_support_to_script_module()` in `lib/client-assets.php`. The method was added in WordPress 6.9, and the plugin requires 7.0. - Document the Navigation block's `$layout` parameter as `mixed`. It comes from block attributes, so the existing `is_array()` guards are needed. - Ignore the `method_exists()` check for `WP_Theme_JSON_Gutenberg::get_viewport_media_queries()` in the Navigation block, which is needed when the block library runs in WordPress Core, where that class is not defined. Remove the 4 resolved entries from the PHPStan baseline. See #86088. * Navigation: Narrow layout param instead of widening it to mixed The only caller of `block_core_navigation_get_layout_custom_property_declarations()` passes the `array_replace()` of two values it has already checked with `is_array()`, so `$layout` is always an array. Document it as `array<string, mixed>` rather than `mixed`, and remove the three redundant `is_array( $layout )` guards. Each offset is still read with `??`, and `justifyContent` is still checked with `is_string()`, so unexpected values fall back to the defaults as before. The return type is narrowed to `array<string, string>`. At the call site, the merged layout gets an inline `@var array<string, mixed>`, since PHPStan types an array narrowed from an attribute as `array<mixed>`, while the layout attribute is a JSON object with string keys. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Add a return shape for layout custom property declarations Every key `block_core_navigation_get_layout_custom_property_declarations()` returns is fixed, and each value comes from a small set of literals, so document them in a `@phpstan-return` array shape. For PHPStan to verify the `--navigation-layout-align` values, the vertical alignment is now looked up in its own map rather than chosen with `in_array()`. PHPStan does not tie the `in_array()` check to the `$justification` it picks, so it inferred that `space-between` could be returned. The behavior is unchanged: `center` and `right` map to `center` and `flex-end`, and everything else falls back to `flex-start`. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Code Quality: Type the script module registry entries Annotate the `$modules` loaded in `gutenberg_define_interactivity_modules_support()` with the shape that wp-build's `generateModuleRegistrationPhp()` writes for each entry: an `id`, `path`, and `asset`, plus `min_only` set to `true` for modules that only ship minified, such as WASM workers. This resolves the `mixed` offset access errors at higher PHPStan levels. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Combine the layout comment with its `@var` docblock Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Code Quality: Add a return shape for viewport media queries `WP_Theme_JSON_Gutenberg::get_viewport_media_queries()` only ever returns the `@mobile`, `@tablet`, and `@desktop` keys, each holding a non-empty `@media` query string, so document that in a `@phpstan-return` array shape. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Skip viewport media queries that are not strings The media queries come from `WP_Theme_JSON_Gutenberg::get_viewport_media_queries()` or, when that is unavailable, from Core's `WP_Theme_JSON::get_viewport_media_queries()`. Core's method only documents a plain `array` return, so the union of the two leaves each `$media_query` as `mixed`. That value is passed as the `rules_group` of the style engine CSS rules, which expects a string. Skip any query that is not a string, so `rules_group` is narrowed to `string` whichever class supplies the queries. Both implementations only return strings, so there is no change in behavior. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Revert "Navigation: Skip viewport media queries that are not strings" This reverts commit aed1887. The guard was only needed because Core's `WP_Theme_JSON::get_viewport_media_queries()` documented a plain `array` return. That method now has an array shape in WordPress/wordpress-develop#15644, so with stubs generated from it, each media query is already a `non-falsy-string` and `rules_group` is narrowed without the check. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Read pull request stubs from the local checkout when it has the commit The stubs generator already read a commit hash from the wordpress-develop checkout that Gutenberg is in, when the checkout has it. A pull request (`--pr`) or a branch (`--commit=trunk`), though, was always fetched from GitHub, since a ref is only resolved to a commit once fetched. That is slow even when the pull request's branch is the one checked out. Resolve the ref to its commit first, so the checkout serves as a cache for these too. The lookup uses the GitHub REST API (`commits/{ref}` with the `vnd.github.sha` media type), which answers in under a second, and handles a branch, a tag, and `refs/pull/N/head` alike. `git ls-remote` was tried first, but for a repository with as many refs as wordpress-develop, it took around 50 seconds. When the lookup fails, or the checkout lacks the commit, the ref is fetched as before. PHP's HTTP stream wrapper ignores the proxy environment variables that Git and curl read, so they are applied to the request, honoring `no_proxy`. Without that, the lookup fails wherever GitHub is only reachable through a proxy, falling back to the slow fetch. A `GITHUB_TOKEN` or `GH_TOKEN` is sent when set, for the higher rate limit of authenticated requests. Generating the stubs from a pull request whose head is checked out drops from about 60 seconds to 6. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Generate the WordPress stubs from WordPress/wordpress-develop#15644 That pull request adds the array shape for `WP_Theme_JSON::get_viewport_media_queries()`, which the Navigation block falls back to, so that its media queries are known to be strings when passed to the style engine as a `rules_group`. Regenerate the stubs with `--commit=trunk` once it is committed to Core, before this is merged. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Revert "PHPStan: Read pull request stubs from the local checkout when it has the commit" This reverts commit 07fd94e. No network request is needed to reuse the local checkout for a pull request. With `--checkout --pr=<number>` and no uncommitted changes, the generator already records the checkout's `HEAD` commit together with the pull request, with the same description and source in composer.json as `--pr` gives, and without fetching anything. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Document generating pull request stubs offline from the checkout The usage and the coding guidelines only said that `--checkout` stubs cannot be generated again in CI, which holds for uncommitted changes alone. Without them, `--checkout --pr=<number>` records the checkout's commit along with the pull request, exactly as `--pr` does, so when the pull request's branch is checked out and pushed, it generates the same stubs without fetching the pull request from GitHub. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Docs: Break up the PHPStan paragraph in the coding guidelines The whole PHPStan guidance was one paragraph, covering what is analyzed, how to run it, the baseline, the WordPress stubs, and local configuration. Give it a subsection of its own with a heading for each topic, put the commands in code blocks, and list the stub generator's options with when to use each. The content is unchanged. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Move the viewport media queries ignore under a level 4 heading The `function.alreadyNarrowedType` error for the Navigation block's `method_exists()` check is reported from rule level 4, but the entry was listed under the level 3 heading, after the `WP_REST_Request` offset entries. Give it a `Level 4:` heading, explain the check in the comment, and list `message` before `identifier`, as the other entries do. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Docs: List the PHPStan commands as alternatives The commands to run the analysis and to regenerate the baseline were each shown together in one code block, which reads as if both should be run, when either one does. List them instead, each with the setup it is for. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Docs: Link the files and directories in the PHPStan guidance Link each file and directory that the PHPStan subsection of the coding guidelines refers to, and name the stubs generator script that the Composer command runs. Per the documentation guidelines, a package README is linked with a root-relative path, which the Block Editor Handbook rewrites. Other files and directories are linked by their full GitHub URLs, as `docs/manifest.json` is, since root-relative links to them would not resolve in the handbook. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Accept any array of layout values instead of asserting string keys The layouts merged for each viewport are narrowed from attribute values with `is_array()`, so PHPStan only knows them as `array<mixed>`. Passing their merge to a parameter typed `array<string, mixed>` needed an inline `@var` asserting string keys, which nothing checks. `block_core_navigation_get_layout_custom_property_declarations()` does not depend on the keys being strings, since it only reads named offsets with `??`. Document `$layout` as `array<mixed>` instead, which is what it is given, and remove the `@var`, so that the call is the same as before. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> --------- Co-authored-by: huzaifaalmesbah <huzaifaalmesbah@git.wordpress.org> Co-authored-by: westonruter <westonruter@git.wordpress.org>
* PHPStan: Resolve always-true type checks - Drop the `method_exists()` check for `WP_Interactivity_API::add_client_navigation_support_to_script_module()` in `lib/client-assets.php`. The method was added in WordPress 6.9, and the plugin requires 7.0. - Document the Navigation block's `$layout` parameter as `mixed`. It comes from block attributes, so the existing `is_array()` guards are needed. - Ignore the `method_exists()` check for `WP_Theme_JSON_Gutenberg::get_viewport_media_queries()` in the Navigation block, which is needed when the block library runs in WordPress Core, where that class is not defined. Remove the 4 resolved entries from the PHPStan baseline. See #86088. * Navigation: Narrow layout param instead of widening it to mixed The only caller of `block_core_navigation_get_layout_custom_property_declarations()` passes the `array_replace()` of two values it has already checked with `is_array()`, so `$layout` is always an array. Document it as `array<string, mixed>` rather than `mixed`, and remove the three redundant `is_array( $layout )` guards. Each offset is still read with `??`, and `justifyContent` is still checked with `is_string()`, so unexpected values fall back to the defaults as before. The return type is narrowed to `array<string, string>`. At the call site, the merged layout gets an inline `@var array<string, mixed>`, since PHPStan types an array narrowed from an attribute as `array<mixed>`, while the layout attribute is a JSON object with string keys. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Add a return shape for layout custom property declarations Every key `block_core_navigation_get_layout_custom_property_declarations()` returns is fixed, and each value comes from a small set of literals, so document them in a `@phpstan-return` array shape. For PHPStan to verify the `--navigation-layout-align` values, the vertical alignment is now looked up in its own map rather than chosen with `in_array()`. PHPStan does not tie the `in_array()` check to the `$justification` it picks, so it inferred that `space-between` could be returned. The behavior is unchanged: `center` and `right` map to `center` and `flex-end`, and everything else falls back to `flex-start`. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Code Quality: Type the script module registry entries Annotate the `$modules` loaded in `gutenberg_define_interactivity_modules_support()` with the shape that wp-build's `generateModuleRegistrationPhp()` writes for each entry: an `id`, `path`, and `asset`, plus `min_only` set to `true` for modules that only ship minified, such as WASM workers. This resolves the `mixed` offset access errors at higher PHPStan levels. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Combine the layout comment with its `@var` docblock Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Code Quality: Add a return shape for viewport media queries `WP_Theme_JSON_Gutenberg::get_viewport_media_queries()` only ever returns the `@mobile`, `@tablet`, and `@desktop` keys, each holding a non-empty `@media` query string, so document that in a `@phpstan-return` array shape. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Skip viewport media queries that are not strings The media queries come from `WP_Theme_JSON_Gutenberg::get_viewport_media_queries()` or, when that is unavailable, from Core's `WP_Theme_JSON::get_viewport_media_queries()`. Core's method only documents a plain `array` return, so the union of the two leaves each `$media_query` as `mixed`. That value is passed as the `rules_group` of the style engine CSS rules, which expects a string. Skip any query that is not a string, so `rules_group` is narrowed to `string` whichever class supplies the queries. Both implementations only return strings, so there is no change in behavior. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Revert "Navigation: Skip viewport media queries that are not strings" This reverts commit aed18876670. The guard was only needed because Core's `WP_Theme_JSON::get_viewport_media_queries()` documented a plain `array` return. That method now has an array shape in WordPress/wordpress-develop#15644, so with stubs generated from it, each media query is already a `non-falsy-string` and `rules_group` is narrowed without the check. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Read pull request stubs from the local checkout when it has the commit The stubs generator already read a commit hash from the wordpress-develop checkout that Gutenberg is in, when the checkout has it. A pull request (`--pr`) or a branch (`--commit=trunk`), though, was always fetched from GitHub, since a ref is only resolved to a commit once fetched. That is slow even when the pull request's branch is the one checked out. Resolve the ref to its commit first, so the checkout serves as a cache for these too. The lookup uses the GitHub REST API (`commits/{ref}` with the `vnd.github.sha` media type), which answers in under a second, and handles a branch, a tag, and `refs/pull/N/head` alike. `git ls-remote` was tried first, but for a repository with as many refs as wordpress-develop, it took around 50 seconds. When the lookup fails, or the checkout lacks the commit, the ref is fetched as before. PHP's HTTP stream wrapper ignores the proxy environment variables that Git and curl read, so they are applied to the request, honoring `no_proxy`. Without that, the lookup fails wherever GitHub is only reachable through a proxy, falling back to the slow fetch. A `GITHUB_TOKEN` or `GH_TOKEN` is sent when set, for the higher rate limit of authenticated requests. Generating the stubs from a pull request whose head is checked out drops from about 60 seconds to 6. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Generate the WordPress stubs from WordPress/wordpress-develop#15644 That pull request adds the array shape for `WP_Theme_JSON::get_viewport_media_queries()`, which the Navigation block falls back to, so that its media queries are known to be strings when passed to the style engine as a `rules_group`. Regenerate the stubs with `--commit=trunk` once it is committed to Core, before this is merged. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Revert "PHPStan: Read pull request stubs from the local checkout when it has the commit" This reverts commit 07fd94e7e86. No network request is needed to reuse the local checkout for a pull request. With `--checkout --pr=<number>` and no uncommitted changes, the generator already records the checkout's `HEAD` commit together with the pull request, with the same description and source in composer.json as `--pr` gives, and without fetching anything. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Document generating pull request stubs offline from the checkout The usage and the coding guidelines only said that `--checkout` stubs cannot be generated again in CI, which holds for uncommitted changes alone. Without them, `--checkout --pr=<number>` records the checkout's commit along with the pull request, exactly as `--pr` does, so when the pull request's branch is checked out and pushed, it generates the same stubs without fetching the pull request from GitHub. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Docs: Break up the PHPStan paragraph in the coding guidelines The whole PHPStan guidance was one paragraph, covering what is analyzed, how to run it, the baseline, the WordPress stubs, and local configuration. Give it a subsection of its own with a heading for each topic, put the commands in code blocks, and list the stub generator's options with when to use each. The content is unchanged. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * PHPStan: Move the viewport media queries ignore under a level 4 heading The `function.alreadyNarrowedType` error for the Navigation block's `method_exists()` check is reported from rule level 4, but the entry was listed under the level 3 heading, after the `WP_REST_Request` offset entries. Give it a `Level 4:` heading, explain the check in the comment, and list `message` before `identifier`, as the other entries do. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Docs: List the PHPStan commands as alternatives The commands to run the analysis and to regenerate the baseline were each shown together in one code block, which reads as if both should be run, when either one does. List them instead, each with the setup it is for. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Docs: Link the files and directories in the PHPStan guidance Link each file and directory that the PHPStan subsection of the coding guidelines refers to, and name the stubs generator script that the Composer command runs. Per the documentation guidelines, a package README is linked with a root-relative path, which the Block Editor Handbook rewrites. Other files and directories are linked by their full GitHub URLs, as `docs/manifest.json` is, since root-relative links to them would not resolve in the handbook. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> * Navigation: Accept any array of layout values instead of asserting string keys The layouts merged for each viewport are narrowed from attribute values with `is_array()`, so PHPStan only knows them as `array<mixed>`. Passing their merge to a parameter typed `array<string, mixed>` needed an inline `@var` asserting string keys, which nothing checks. `block_core_navigation_get_layout_custom_property_declarations()` does not depend on the keys being strings, since it only reads named offsets with `??`. Document `$layout` as `array<mixed>` instead, which is what it is given, and remove the `@var`, so that the call is the same as before. Co-Authored-By: Claude Opus 5.5 (medium effort) <noreply@anthropic.com> --------- Co-authored-by: huzaifaalmesbah <huzaifaalmesbah@git.wordpress.org> Co-authored-by: westonruter <westonruter@git.wordpress.org> Source: WordPress/gutenberg@8f625ba
✅ Committed in r65318 (28d2f50).
This defines array shapes for block type selectors and for viewport media queries, which Gutenberg PRs depend on (see below).
Block type selectors
WP_Block_Type::$selectorsis documented as a plainarray. This defines its shape, following theselectorsschema inblock.json: an optionalrootselector string, plus selectors for block support features (such ascolorortypography), each either a selector string or an array of selectors keyed by subfeature (such asrootortext).The shape is unsealed, so any feature key is allowed. Core itself uses keys the schema doesn't list (
statesandfilter), and third-party blocks can add their own. The plain form matches whatWP_Theme_JSON::get_feature_selector()already uses.The shape is applied to:
WP_Block_Type::$selectorsproperty, whose description now also covers thestateskey, along with theselectorsargument to the constructor.WP_Theme_JSON::get_block_selectors(), whose$block_typeparameter is also now documented asWP_Block_Typerather thanobject.selectorskey of the$metadataparameter ofWP_Theme_JSON::get_feature_declarations_for_node().$block_selectorsparameter ofwp_get_state_style_groups().There is one code change.
WP_Theme_JSON::get_block_selectors()now builds the feature selectors first and prependsrootwitharray( 'root' => $root_selector ) + $feature_selectors. The result has the same keys in the same order, but sinceBLOCK_SUPPORT_FEATURE_LEVEL_SELECTORSis accessed viastatic::, a subclass's feature namedrootcan no longer replace the root selector string with an array.New tests in
Tests_Theme_wpThemeJsoncoverget_block_selectors():test_get_block_selectors_builds_default_selectors_with_root_first(): For a block without custom selectors, the default selectors listrootfirst, followed by the feature selectors in the order ofBLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS.test_get_block_selectors_returns_custom_selectors(): Custom selectors set by the block are returned as they are.test_get_block_selectors_keeps_root_selector_string(): For a subclass that maps a support to a feature namedroot, the root selector string is kept.Building the default selectors with
$feature_selectors['root'] ??= $root_selectorinstead fails the first and third tests.The
$state_styleparameter ofwp_get_state_style_groups()is also now documented asarray<string, mixed>, since it is keyed by style feature and each key is looked up in$block_selectors.At rule level 10, these changes resolve 10 PHPStan errors, including
missingType.iterableValueon the property and on the two functions,return.typeformixedreturned fromwp_get_block_css_selector()andWP_Theme_JSON::get_block_selectors(), andargument.typeformixedpassed towp_add_state_style_group(). They introduce one: the callerwp_get_block_state_style_rules()narrows each state style frommixedonly withis_array(), so it now reports anargument.typeerror for passingarray<mixed, mixed>as$state_style. That is a more accurate account of the untyped theme.json data than themissingType.iterableValueerror it replaces. None of these errors are in the level 5 baselines.Viewport media queries
WP_Theme_JSON::get_viewport_media_queries()only ever returns the@mobile,@tablet, and@desktopkeys, each holding a non-empty@mediaquery string, so this documents that in a@phpstan-returnarray shape:array{ '@mobile'?: non-falsy-string, '@tablet'?: non-falsy-string, '@desktop'?: non-falsy-string, }Without it, callers see each media query as
mixed. The Navigation block passes them towp_style_engine_get_stylesheet_from_css_rules()as therules_group, which expects a string.Gutenberg PRs depending on this
These Gutenberg PRs generate their WordPress stubs for PHPStan from this PR, so that the analysis sees these types:
WP_Theme_JSON_Gutenbergandgutenberg_get_state_style_groups().Once this is committed, they will need their stubs regenerated from trunk before they are merged.
Trac ticket: https://core.trac.wordpress.org/ticket/65817
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5 (medium effort)
Used for: Deriving the array shape from the
block.jsonschema and the code that reads the selectors, applying it, comparing PHPStan results before and after, running the related PHPUnit tests, and writing this description. Directed and reviewed by me.This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.