Make WordPress Core

Changeset 64727


Ignore:
Timestamp:
10/26/2026 02:17:56 AM (32 hours ago)
Author:
westonruter
Message:

Code Quality: Sync PHPStan fixes from Gutenberg.

These fixes were made in Gutenberg while resolving the PHPStan errors in the plugin, ahead of adding PHPStan to its CI, and are brought to core to keep the two codebases in sync. They were developed in:

The first one fixes a bug in core. Since r56046 the array_key_exists() checks in wp_register_shadow_support() have lacked their negation, so a block type with shadow support but no style or shadow attribute never had them registered, while one that already defined them had its definitions replaced, dropping any default. Gutenberg already had the negation. Tests are added for the attribute registration.

The others make implicit type conversions explicit in the layout and duotone code, and narrow the types used by WP_Theme_JSON::get_feature_declarations_for_node() so that it reports no errors even at PHPStan rule level 10. Since the node comes from theme.json data, that method now also checks that a feature is an array before reading its subfeatures. This fixes a fatal error ("Cannot unset string offsets") when a feature's value is a string, such as a shadow, and the block's selectors for that feature are a list rather than keyed by subfeature name, since each numeric key then reads a character of the string instead of a subfeature. The baseline entries for the resolved errors are removed.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/14862.
Follow-up to r56046, r56058, r56101, r62478, r63426.

Props justlevine, westonruter, poena, ramonopoly.
See #65817.

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-supports/layout.php

    r64354 r64727  
    238238                $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
    239239                $parent_column_value = floatval( $parent_column_width );
    240                 $parent_column_unit  = explode( $parent_column_value, $parent_column_width );
     240                $parent_column_unit  = explode( (string) $parent_column_value, $parent_column_width );
    241241
    242242                $num_cols_to_break_at = 2;
  • trunk/src/wp-includes/block-supports/shadow.php

    r63529 r64727  
    2626        }
    2727
    28         if ( array_key_exists( 'style', $block_type->attributes ) ) {
     28        if ( ! array_key_exists( 'style', $block_type->attributes ) ) {
    2929                $block_type->attributes['style'] = array(
    3030                        'type' => 'object',
    … …  
    3232        }
    3333
    34         if ( array_key_exists( 'shadow', $block_type->attributes ) ) {
     34        if ( ! array_key_exists( 'shadow', $block_type->attributes ) ) {
    3535                $block_type->attributes['shadow'] = array(
    3636                        'type' => 'string',
  • trunk/src/wp-includes/class-wp-duotone.php

    r64493 r64727  
    497497                $hsla = self::colord_clamp_hsla(
    498498                        array(
    499                                 'h' => self::colord_parse_hue( $match[1], $match[2] ),
     499                                'h' => self::colord_parse_hue( (float) $match[1], $match[2] ),
    500500                                's' => (float) $match[3],
    501501                                'l' => (float) $match[4],
  • trunk/src/wp-includes/class-wp-theme-json.php

    r64683 r64727  
    2525         * @since 5.8.0
    2626         * @var array
     27         * @phpstan-var array{ version?: int, settings?: array<string, mixed>, styles?: array<string, mixed>, ...<string, mixed> }
    2728         */
    2829        protected $theme_json = null;
    … …  
    31883189         * @param bool   $use_root_padding Whether to add custom properties at root level.
    31893190         * @return array Returns the modified $declarations.
     3191         *
     3192         * @phpstan-return list<array{ name: non-falsy-string, value: scalar }>
    31903193         */
    31913194        protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) {
    … …  
    60536056         * @since 6.3.0
    60546057         *
    6055          * @param array $metadata The related block metadata containing selectors.
    6056          * @param array $node     A merged theme.json node for block or variation. Features
    6057          *                        promoted to their own selector are removed from it.
    6058          * @return array The style declarations for the node's features with custom
    6059          *               selectors.
     6058         * @param array<string, mixed> $metadata The related block metadata containing selectors.
     6059         * @param array<string, mixed> $node     A merged theme.json node for block or variation. Features
     6060         *                                       promoted to their own selector are removed from it.
     6061         * @return array<string, array[]> The style declarations for the node's features with custom
     6062         *                                selectors, keyed by selector.
     6063         *
     6064         * @phpstan-param array{ selectors?: array<string, string|array<string, string>> } $metadata
     6065         * @phpstan-return array<string, list<array{ name: non-falsy-string, value: scalar }>>
    60606066         */
    60616067        protected function get_feature_declarations_for_node( $metadata, &$node ) {
    … …  
    60776083                        }
    60786084
    6079                         if ( is_array( $feature_selectors ) ) {
     6085                        if ( is_array( $feature_selectors ) && is_array( $node[ $feature ] ) ) {
     6086                                $feature_styles = $node[ $feature ];
    60806087                                foreach ( $feature_selectors as $subfeature => $subfeature_selector ) {
    6081                                         if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) {
     6088                                        if ( 'root' === $subfeature || empty( $feature_styles[ $subfeature ] ) ) {
    60826089                                                continue;
    60836090                                        }
    … …  
    60896096                                        $subfeature_node = array(
    60906097                                                $feature => array(
    6091                                                         $subfeature => $node[ $feature ][ $subfeature ],
     6098                                                        $subfeature => $feature_styles[ $subfeature ],
    60926099                                                ),
    60936100                                        );
    … …  
    61106117                                         * block's.
    61116118                                         */
    6112                                         unset( $node[ $feature ][ $subfeature ] );
    6113                                 }
     6119                                        unset( $feature_styles[ $subfeature ] );
     6120                                }
     6121                                $node[ $feature ] = $feature_styles;
    61146122                        }
    61156123
  • trunk/tests/phpstan/baselines/argument.type.neon

    r64670 r64727  
    655655                        path: ../../../src/wp-includes/author-template.php
    656656                -
    657                         message: '#^Parameter \#1 \$separator of function explode expects string, float given\.$#'
    658                         identifier: argument.type
    659                         count: 1
    660                         path: ../../../src/wp-includes/block-supports/layout.php
    661                 -
    662657                        message: '#^Parameter \#1 \$block of function filter_block_kses expects WP_Block_Parser_Block, array given\.$#'
    663658                        identifier: argument.type
    … …  
    735730                        path: ../../../src/wp-includes/class-wp-date-query.php
    736731                -
    737                         message: '#^Parameter \#1 \$value of static method WP_Duotone\:\:colord_parse_hue\(\) expects float, string given\.$#'
    738                         identifier: argument.type
    739                         count: 1
    740                         path: ../../../src/wp-includes/class-wp-duotone.php
    741                 -
    742732                        message: '#^Parameter \#1 \$postarr of function wp_insert_post expects array\{ID\?\: int, post_author\?\: int, post_date\?\: string, post_date_gmt\?\: string, post_content\?\: string, post_content_filtered\?\: string, post_title\?\: string, post_excerpt\?\: string, \.\.\., \.\.\.\}, array\<string\> given\.$#'
    743733                        identifier: argument.type
  • trunk/tests/phpunit/tests/block-supports/shadow.php

    r64449 r64727  
    44 *
    55 * @covers ::wp_apply_shadow_support
     6 *
     7 * @phpstan-type Shadow_Attributes array{
     8 *     style?: array{ type: 'object', default?: array{ shadow: non-empty-string } },
     9 *     shadow?: array{ type: 'string', default?: non-empty-string },
     10 * }
    611 */
    712class Tests_Block_Supports_Shadow extends WP_UnitTestCase {
    … …  
    4954
    5055        /**
     56         * Tests that shadow block support attribute registration works as expected.
     57         *
     58         * @ticket 65817
     59         *
     60         * @covers ::wp_register_shadow_support
     61         *
     62         * @dataProvider data_wp_register_shadow_support
     63         *
     64         * @param bool                   $support  Shadow block support configuration.
     65         * @param Shadow_Attributes|null $value    Attributes array for the block.
     66         * @param Shadow_Attributes      $expected Expected attributes for the block.
     67         */
     68        public function test_wp_register_shadow_support( bool $support, ?array $value, array $expected ) {
     69                $this->test_block_name = 'test/shadow-attributes';
     70                register_block_type(
     71                        $this->test_block_name,
     72                        array(
     73                                'api_version' => 3,
     74                                'supports'    => array( 'shadow' => $support ),
     75                                'attributes'  => $value,
     76                        )
     77                );
     78                $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $this->test_block_name );
     79                $this->assertInstanceOf( WP_Block_Type::class, $block_type );
     80
     81                wp_register_shadow_support( $block_type );
     82
     83                $actual = $block_type->attributes;
     84                $this->assertIsArray( $actual );
     85                $expected = array_merge( WP_Block_Type::GLOBAL_ATTRIBUTES, $expected );
     86                $this->assertSameSetsWithIndex( $expected, $actual );
     87        }
     88
     89        /**
     90         * Data provider for {@see self::test_wp_register_shadow_support()}.
     91         *
     92         * @return array<non-falsy-string, array{ support: bool, value: Shadow_Attributes|null, expected: Shadow_Attributes }>
     93         */
     94        public static function data_wp_register_shadow_support(): array {
     95                return array(
     96                        'style and shadow attributes are registered when block supports shadow' => array(
     97                                'support'  => true,
     98                                'value'    => null,
     99                                'expected' => array(
     100                                        'style'  => array(
     101                                                'type' => 'object',
     102                                        ),
     103                                        'shadow' => array(
     104                                                'type' => 'string',
     105                                        ),
     106                                ),
     107                        ),
     108                        'no attributes are registered when block does not support shadow' => array(
     109                                'support'  => false,
     110                                'value'    => null,
     111                                'expected' => array(),
     112                        ),
     113                        'existing style and shadow attributes are not overwritten' => array(
     114                                'support'  => true,
     115                                'value'    => array(
     116                                        'style'  => array(
     117                                                'type'    => 'object',
     118                                                'default' => array(
     119                                                        'shadow' => 'var:preset|shadow|natural',
     120                                                ),
     121                                        ),
     122                                        'shadow' => array(
     123                                                'type'    => 'string',
     124                                                'default' => 'natural',
     125                                        ),
     126                                ),
     127                                'expected' => array(
     128                                        'style'  => array(
     129                                                'type'    => 'object',
     130                                                'default' => array(
     131                                                        'shadow' => 'var:preset|shadow|natural',
     132                                                ),
     133                                        ),
     134                                        'shadow' => array(
     135                                                'type'    => 'string',
     136                                                'default' => 'natural',
     137                                        ),
     138                                ),
     139                        ),
     140                );
     141        }
     142
     143        /**
    51144         * Tests the generation of shadow block support styles.
    52145         *
Note: See TracChangeset for help on using the changeset viewer.