Make WordPress Core

Changeset 58321


Ignore:
Timestamp:
06/04/2024 07:16:48 AM (2 years ago)
Author:
gziolo
Message:

Interactivity API: Print debug warning when server directives processing encounters errors

Aims to improve the developer experience of the Interactivity API server directives processing.

Props cbravobernal, jonsurrell, westonruter, darerodz, czapla, gziolo.
Fixes #61044.

Location:
trunk
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php

    r58233 r58321  
    199199                $depth    = 1;
    200200                $tag_name = $this->get_tag();
    201                 while ( $depth > 0 && $this->next_tag(
    202                         array(
    203                                 'tag_name'    => $tag_name,
    204                                 'tag_closers' => 'visit',
    205                         )
    206                 ) ) {
    207                         if ( $this->has_self_closing_flag() ) {
    208                                 continue;
    209                         }
    210                         $depth += $this->is_tag_closer() ? -1 : 1;
     201
     202                while ( $depth > 0 && $this->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
     203                        if ( ! $this->is_tag_closer() && $this->get_attribute_names_with_prefix( 'data-wp-' ) ) {
     204                                /* translators: 1: SVG or MATH HTML tag. */
     205                                $message = sprintf( __( 'Interactivity directives were detected inside an incompatible %1$s tag. These directives will be ignored in the server side render.' ), $tag_name );
     206                                _doing_it_wrong( __METHOD__, $message, '6.6.0' );
     207                        }
     208                        if ( $this->get_tag() === $tag_name ) {
     209                                if ( $this->has_self_closing_flag() ) {
     210                                        continue;
     211                                }
     212                                $depth += $this->is_tag_closer() ? -1 : 1;
     213                        }
    211214                }
    212215
  • trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api.php

    r58320 r58321  
    273273         *
    274274         * @since 6.5.0
     275         * @since 6.6.0 The function displays a warning message when the HTML contains unbalanced tags or a directive appears in a MATH or SVG tag.
    275276         *
    276277         * @param string $html            The HTML content to process.
     
    296297                         */
    297298                        if ( 'SVG' === $tag_name || 'MATH' === $tag_name ) {
     299                                if ( $p->get_attribute_names_with_prefix( 'data-wp-' ) ) {
     300                                        /* translators: 1: SVG or MATH HTML tag, 2: Namespace of the interactive block. */
     301                                        $message = sprintf( __( 'Interactivity directives were detected on an incompatible %1$s tag when processing "%2$s". These directives will be ignored in the server side render.' ), $tag_name, end( $namespace_stack ) );
     302                                        _doing_it_wrong( __METHOD__, $message, '6.6.0' );
     303                                }
    298304                                $p->skip_to_tag_closer();
    299305                                continue;
     
    383389                        }
    384390                }
    385 
    386391                /*
    387392                 * It returns null if the HTML is unbalanced because unbalanced HTML is
    388393                 * not safe to process. In that case, the Interactivity API runtime will
    389                  * update the HTML on the client side during the hydration.
     394                 * update the HTML on the client side during the hydration. It will also
     395                 * display a notice to the developer to inform them about the issue.
    390396                 */
    391                 return $unbalanced || 0 < count( $tag_stack ) ? null : $p->get_updated_html();
     397                if ( $unbalanced || 0 < count( $tag_stack ) ) {
     398                        $tag_errored = 0 < count( $tag_stack ) ? end( $tag_stack )[0] : $tag_name;
     399                        /* translators: %1s: Namespace processed, %2s: The tag that caused the error; could be any HTML tag.  */
     400                        $message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $namespace_stack ), $tag_errored );
     401                        _doing_it_wrong( __METHOD__, $message, '6.6.0' );
     402                        return null;
     403                }
     404
     405                return $p->get_updated_html();
    392406        }
    393407
     
    397411         *
    398412         * @since 6.5.0
     413         * @since 6.6.0 The function now adds a warning when the namespace is null, falsy, or the directive value is empty.
    399414         *
    400415         * @param string|true $directive_value   The directive attribute value string or `true` when it's a boolean attribute.
     
    403418         * @param array|false $context           The current context for evaluating the directive or false if there is no
    404419         *                                       context.
    405          * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist.
     420         * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist or the namespace is falsy.
    406421         */
    407422        private function evaluate( $directive_value, string $default_namespace, $context = false ) {
    408423                list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace );
    409                 if ( empty( $path ) ) {
     424                if ( ! $ns || ! $path ) {
     425                        /* translators: %s: The directive value referenced. */
     426                        $message = sprintf( __( 'Namespace or reference path cannot be empty. Directive value referenced: %s' ), $directive_value );
     427                        _doing_it_wrong( __METHOD__, $message, '6.6.0' );
    410428                        return null;
    411429                }
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-bind.php

    r57836 r58321  
    155155         *
    156156         * @covers ::process_directives
     157         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
    157158         */
    158159        public function test_wp_bind_ignores_empty_value() {
     
    168169         *
    169170         * @covers ::process_directives
     171         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
    170172         */
    171173        public function test_wp_bind_ignores_without_value() {
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php

    r57836 r58321  
    238238         *
    239239         * @covers ::process_directives
     240         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
    240241         */
    241242        public function test_wp_class_doesnt_change_class_attribute_with_empty_value() {
     
    252253         *
    253254         * @covers ::process_directives
     255         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
    254256         */
    255257        public function test_wp_class_doesnt_change_class_attribute_without_value() {
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php

    r57836 r58321  
    318318         *
    319319         * @covers ::process_directives
     320         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
    320321         */
    321322        public function test_wp_context_directive_doesnt_work_without_any_namespace() {
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php

    r57836 r58321  
    581581         *
    582582         * @covers ::process_directives
     583         *
     584         * @expectedIncorrectUsage WP_Interactivity_API::process_directives_args
    583585         */
    584586        public function test_wp_each_unbalanced_tags() {
     
    599601         *
    600602         * @covers ::process_directives
     603         *
     604         * @expectedIncorrectUsage WP_Interactivity_API::process_directives_args
    601605         */
    602606        public function test_wp_each_unbalanced_tags_in_nested_template_tags() {
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php

    r57836 r58321  
    366366         *
    367367         * @covers ::process_directives
     368         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
    368369         */
    369370        public function test_wp_style_doesnt_change_style_attribute_with_empty_value() {
     
    380381         *
    381382         * @covers ::process_directives
     383         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
    382384         */
    383385        public function test_wp_style_doesnt_change_style_attribute_without_value() {
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php

    r57836 r58321  
    1313 * @group interactivity-api
    1414 */
    15 class Tests_WP_Interactivity_API_WP_Text extends WP_UnitTestCase {
     15class Tests_Interactivity_API_WpInteractivityAPIWPText extends WP_UnitTestCase {
    1616        /**
    1717         * Instance of WP_Interactivity_API.
     
    132132         *
    133133         * @covers ::process_directives
     134         *
     135         * @expectedIncorrectUsage WP_Interactivity_API::process_directives_args
    134136         */
    135137        public function test_wp_text_fails_with_unbalanced_and_same_tags_inside_content() {
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php

    r58320 r58321  
    650650         * @dataProvider data_html_with_unbalanced_tags
    651651         *
     652         * @expectedIncorrectUsage WP_Interactivity_API::process_directives_args
     653         *
    652654         * @param string $html HTML containing unbalanced tags and also a directive.
    653655         */
     
    697699                $html           = '
    698700                        <header>
    699                                 <svg height="100" data-wp-bind--width="myPlugin::state.width">
     701                                <svg height="100">
    700702                                        <title>Red Circle</title>
    701703                                        <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
    702704                                </svg>
    703705                                <div data-wp-bind--id="myPlugin::state.id"></div>
    704                                 <div data-wp-bind--id="myPlugin::state.width"></div>
    705706                        </header>
    706707                ';
    707708                $processed_html = $this->interactivity->process_directives( $html );
    708709                $p              = new WP_HTML_Tag_Processor( $processed_html );
    709                 $p->next_tag( 'svg' );
    710                 $this->assertNull( $p->get_attribute( 'width' ) );
    711710                $p->next_tag( 'div' );
    712711                $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) );
    713                 $p->next_tag( 'div' );
    714                 $this->assertEquals( '100', $p->get_attribute( 'id' ) );
    715712        }
    716713
     
    722719         *
    723720         * @covers ::process_directives
     721         * @expectedIncorrectUsage WP_Interactivity_API_Directives_Processor::skip_to_tag_closer
    724722         */
    725723        public function test_process_directives_does_not_change_inner_html_in_svgs() {
     
    751749         *
    752750         * @covers ::process_directives
     751         * @expectedIncorrectUsage WP_Interactivity_API::process_directives_args
    753752         */
    754753        public function test_process_directives_change_html_if_contains_math() {
     
    785784         *
    786785         * @covers ::process_directives
     786         * @expectedIncorrectUsage WP_Interactivity_API::process_directives_args
     787         * @expectedIncorrectUsage WP_Interactivity_API_Directives_Processor::skip_to_tag_closer
    787788         */
    788789        public function test_process_directives_does_not_change_inner_html_in_math() {
     
    812813         * Invokes the private `evaluate` method of WP_Interactivity_API class.
    813814         *
    814          * @param string $directive_value The directive attribute value to evaluate.
     815         * @param string $directive_value   The directive attribute value to evaluate.
     816         * @param string $default_namespace The default namespace used with directives.
    815817         * @return mixed The result of the evaluate method.
    816818         */
    817         private function evaluate( $directive_value ) {
     819        private function evaluate( $directive_value, $default_namespace = 'myPlugin' ) {
    818820                $generate_state = function ( $name ) {
    819821                        $obj       = new stdClass();
     
    848850                $evaluate = new ReflectionMethod( $this->interactivity, 'evaluate' );
    849851                $evaluate->setAccessible( true );
    850                 return $evaluate->invokeArgs( $this->interactivity, array( $directive_value, 'myPlugin', $context ) );
     852                return $evaluate->invokeArgs( $this->interactivity, array( $directive_value, $default_namespace, $context ) );
    851853        }
    852854
     
    946948                $result = $this->evaluate( 'otherPlugin::context.nested.key' );
    947949                $this->assertEquals( 'otherPlugin-context-nested', $result );
     950        }
     951
     952        /**
     953         * Tests the `evaluate` method for non valid namespace values.
     954         *
     955         * @ticket 61044
     956         *
     957         * @covers ::evaluate
     958         * @expectedIncorrectUsage WP_Interactivity_API::evaluate
     959         */
     960        public function test_evaluate_unvalid_namespaces() {
     961                $result = $this->evaluate( 'path', 'null' );
     962                $this->assertNull( $result );
     963
     964                $result = $this->evaluate( 'path', '' );
     965                $this->assertNull( $result );
     966
     967                $result = $this->evaluate( 'path', '{}' );
     968                $this->assertNull( $result );
    948969        }
    949970
Note: See TracChangeset for help on using the changeset viewer.