From 962a7e8b1172db15252e8cf213e0bcd7b9a8fa80 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 14:47:29 -0700 Subject: [PATCH 1/9] HTML API: Remove the outer loop the adoption agency algorithm never repeats. `WP_HTML_Processor::run_adoption_agency_algorithm()` wrapped its steps in a loop with a budget and an outer loop counter, mirroring the spec. But only the first pass of the algorithm is implemented, and every path through it either returns or calls `bail()`, which never returns. The loop has never run a second time since it was introduced, so its counter checks were constant and the `bail()` after it was unreachable. PHPStan 2.3 now reports both comparisons as constant, which had been baselined as `greater.alwaysTrue` and `greaterOrEqual.alwaysFalse`. The loop, its budget, and the unreachable `bail()` are removed, and the body runs once as it always has. A comment marks where the spec's outer loop belongs, to be restored along with the steps that would start another pass. Both baselines are deleted, and the `deadCode.unreachable` count for the file drops by one for the removed `bail()`. The html-api and html-api-web-platform-tests groups pass, with the same 1725 tests and 449 skips as before the change. Co-Authored-By: Claude Opus 5.5 --- phpstan.neon.dist | 2 - .../html-api/class-wp-html-processor.php | 133 +++++++++--------- .../baselines/deadCode.unreachable.neon | 2 +- .../phpstan/baselines/greater.alwaysTrue.neon | 25 ---- .../baselines/greaterOrEqual.alwaysFalse.neon | 25 ---- 5 files changed, 67 insertions(+), 120 deletions(-) delete mode 100644 tests/phpstan/baselines/greater.alwaysTrue.neon delete mode 100644 tests/phpstan/baselines/greaterOrEqual.alwaysFalse.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e61c7d525fe91..b8eb7a5ccd898 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -42,8 +42,6 @@ includes: - tests/phpstan/baselines/function.alreadyNarrowedType.neon - tests/phpstan/baselines/function.impossibleType.neon - tests/phpstan/baselines/function.resultUnused.neon - - tests/phpstan/baselines/greater.alwaysTrue.neon - - tests/phpstan/baselines/greaterOrEqual.alwaysFalse.neon - tests/phpstan/baselines/greaterOrEqual.alwaysTrue.neon - tests/phpstan/baselines/identical.alwaysFalse.neon - tests/phpstan/baselines/identical.alwaysTrue.neon diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index c278cca8b0e23..66c4415d25075 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -6096,7 +6096,6 @@ private function reset_insertion_mode_appropriately(): void { * @see https://html.spec.whatwg.org/#adoption-agency-algorithm */ private function run_adoption_agency_algorithm(): void { - $budget = 1000; $subject = $this->get_tag(); $current_node = $this->state->stack_of_open_elements->current_node(); @@ -6110,88 +6109,88 @@ private function run_adoption_agency_algorithm(): void { return; } - $outer_loop_counter = 0; - while ( $budget-- > 0 ) { - if ( $outer_loop_counter++ >= 8 ) { - return; - } - - /* - * > Let formatting element be the last element in the list of active formatting elements that: - * > - is between the end of the list and the last marker in the list, - * > if any, or the start of the list otherwise, - * > - and has the tag name subject. - */ - $formatting_element = null; - foreach ( $this->state->active_formatting_elements->walk_up() as $item ) { - if ( 'marker' === $item->node_name ) { - break; - } + /* + * > Let outer loop counter be 0. + * > Outer loop: If outer loop counter is greater than or equal to 8, then return. + * + * Only the first pass through the outer loop is supported. Every path through it + * either returns or bails before reaching the steps that would start another pass, + * so the loop and its counter are left out until those steps are implemented. + */ - if ( $subject === $item->node_name ) { - $formatting_element = $item; - break; - } + /* + * > Let formatting element be the last element in the list of active formatting elements that: + * > - is between the end of the list and the last marker in the list, + * > if any, or the start of the list otherwise, + * > - and has the tag name subject. + */ + $formatting_element = null; + foreach ( $this->state->active_formatting_elements->walk_up() as $item ) { + if ( 'marker' === $item->node_name ) { + break; } - // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. - if ( null === $formatting_element ) { - $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); + if ( $subject === $item->node_name ) { + $formatting_element = $item; + break; } + } - // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. - if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) { - $this->state->active_formatting_elements->remove_node( $formatting_element ); - return; - } + // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. + if ( null === $formatting_element ) { + $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); + } - // > If formatting element is in the stack of open elements, but the element is not in scope, then this is a parse error; return. - if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->node_name ) ) { - return; - } + // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. + if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) { + $this->state->active_formatting_elements->remove_node( $formatting_element ); + return; + } - /* - * > Let furthest block be the topmost node in the stack of open elements that is lower in the stack - * > than formatting element, and is an element in the special category. There might not be one. - */ - $is_above_formatting_element = true; - $furthest_block = null; - foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) { - if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) { - continue; - } + // > If formatting element is in the stack of open elements, but the element is not in scope, then this is a parse error; return. + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->node_name ) ) { + return; + } - if ( $is_above_formatting_element ) { - $is_above_formatting_element = false; - continue; - } + /* + * > Let furthest block be the topmost node in the stack of open elements that is lower in the stack + * > than formatting element, and is an element in the special category. There might not be one. + */ + $is_above_formatting_element = true; + $furthest_block = null; + foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) { + if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) { + continue; + } - if ( self::is_special( $item ) ) { - $furthest_block = $item; - break; - } + if ( $is_above_formatting_element ) { + $is_above_formatting_element = false; + continue; } - /* - * > If there is no furthest block, then the UA must first pop all the nodes from the bottom of the - * > stack of open elements, from the current node up to and including formatting element, then - * > remove formatting element from the list of active formatting elements, and finally return. - */ - if ( null === $furthest_block ) { - foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { - $this->state->stack_of_open_elements->pop(); + if ( self::is_special( $item ) ) { + $furthest_block = $item; + break; + } + } - if ( $formatting_element->bookmark_name === $item->bookmark_name ) { - $this->state->active_formatting_elements->remove_node( $formatting_element ); - return; - } + /* + * > If there is no furthest block, then the UA must first pop all the nodes from the bottom of the + * > stack of open elements, from the current node up to and including formatting element, then + * > remove formatting element from the list of active formatting elements, and finally return. + */ + if ( null === $furthest_block ) { + foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { + $this->state->stack_of_open_elements->pop(); + + if ( $formatting_element->bookmark_name === $item->bookmark_name ) { + $this->state->active_formatting_elements->remove_node( $formatting_element ); + return; } } - - $this->bail( 'Cannot extract common ancestor in adoption agency algorithm.' ); } - $this->bail( 'Cannot run adoption agency when looping required.' ); + $this->bail( 'Cannot extract common ancestor in adoption agency algorithm.' ); } /** diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 2282799c3903d..1266b6472ec1e 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 31 + count: 30 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' diff --git a/tests/phpstan/baselines/greater.alwaysTrue.neon b/tests/phpstan/baselines/greater.alwaysTrue.neon deleted file mode 100644 index 897d88c5ee83f..0000000000000 --- a/tests/phpstan/baselines/greater.alwaysTrue.neon +++ /dev/null @@ -1,25 +0,0 @@ -# PHPStan baseline for the `greater.alwaysTrue` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/greater.alwaysTrue -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=greater.alwaysTrue -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Comparison operation "\>" between 999\|1000 and 0 is always true\.$#' - identifier: greater.alwaysTrue - count: 1 - path: ../../../src/wp-includes/html-api/class-wp-html-processor.php diff --git a/tests/phpstan/baselines/greaterOrEqual.alwaysFalse.neon b/tests/phpstan/baselines/greaterOrEqual.alwaysFalse.neon deleted file mode 100644 index 8010e78a6979a..0000000000000 --- a/tests/phpstan/baselines/greaterOrEqual.alwaysFalse.neon +++ /dev/null @@ -1,25 +0,0 @@ -# PHPStan baseline for the `greaterOrEqual.alwaysFalse` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/greaterOrEqual.alwaysFalse -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=greaterOrEqual.alwaysFalse -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Comparison operation "\>\=" between 0 and 8 is always false\.$#' - identifier: greaterOrEqual.alwaysFalse - count: 1 - path: ../../../src/wp-includes/html-api/class-wp-html-processor.php From 8b722d4718d71cc1d6f6d2b5ac1a25c2e18e4bfa Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 22:48:29 -0700 Subject: [PATCH 2/9] Revert changes to WP_HTML_Tag_Processor and add errors to non-baseline ignore list --- phpstan.neon.dist | 13 ++ .../html-api/class-wp-html-processor.php | 133 +++++++++--------- .../baselines/deadCode.unreachable.neon | 2 +- 3 files changed, 81 insertions(+), 67 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b8eb7a5ccd898..d6ba7f5511b3b 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -150,3 +150,16 @@ 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 + + # HTML API: + # These errors are ignored for the sake of making it easier to keep the HTML Processor in sync with the HTML spec. + - + message: '#^Comparison operation "\>" between 999\|1000 and 0 is always true\.$#' + identifier: greater.alwaysTrue + count: 1 + path: src/wp-includes/html-api/class-wp-html-processor.php + - + message: '#^Comparison operation "\>\=" between 0 and 8 is always false\.$#' + identifier: greaterOrEqual.alwaysFalse + count: 1 + path: src/wp-includes/html-api/class-wp-html-processor.php diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 66c4415d25075..c278cca8b0e23 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -6096,6 +6096,7 @@ private function reset_insertion_mode_appropriately(): void { * @see https://html.spec.whatwg.org/#adoption-agency-algorithm */ private function run_adoption_agency_algorithm(): void { + $budget = 1000; $subject = $this->get_tag(); $current_node = $this->state->stack_of_open_elements->current_node(); @@ -6109,88 +6110,88 @@ private function run_adoption_agency_algorithm(): void { return; } - /* - * > Let outer loop counter be 0. - * > Outer loop: If outer loop counter is greater than or equal to 8, then return. - * - * Only the first pass through the outer loop is supported. Every path through it - * either returns or bails before reaching the steps that would start another pass, - * so the loop and its counter are left out until those steps are implemented. - */ - - /* - * > Let formatting element be the last element in the list of active formatting elements that: - * > - is between the end of the list and the last marker in the list, - * > if any, or the start of the list otherwise, - * > - and has the tag name subject. - */ - $formatting_element = null; - foreach ( $this->state->active_formatting_elements->walk_up() as $item ) { - if ( 'marker' === $item->node_name ) { - break; + $outer_loop_counter = 0; + while ( $budget-- > 0 ) { + if ( $outer_loop_counter++ >= 8 ) { + return; } - if ( $subject === $item->node_name ) { - $formatting_element = $item; - break; - } - } - - // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. - if ( null === $formatting_element ) { - $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); - } + /* + * > Let formatting element be the last element in the list of active formatting elements that: + * > - is between the end of the list and the last marker in the list, + * > if any, or the start of the list otherwise, + * > - and has the tag name subject. + */ + $formatting_element = null; + foreach ( $this->state->active_formatting_elements->walk_up() as $item ) { + if ( 'marker' === $item->node_name ) { + break; + } - // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. - if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) { - $this->state->active_formatting_elements->remove_node( $formatting_element ); - return; - } + if ( $subject === $item->node_name ) { + $formatting_element = $item; + break; + } + } - // > If formatting element is in the stack of open elements, but the element is not in scope, then this is a parse error; return. - if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->node_name ) ) { - return; - } + // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. + if ( null === $formatting_element ) { + $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); + } - /* - * > Let furthest block be the topmost node in the stack of open elements that is lower in the stack - * > than formatting element, and is an element in the special category. There might not be one. - */ - $is_above_formatting_element = true; - $furthest_block = null; - foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) { - if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) { - continue; + // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. + if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) { + $this->state->active_formatting_elements->remove_node( $formatting_element ); + return; } - if ( $is_above_formatting_element ) { - $is_above_formatting_element = false; - continue; + // > If formatting element is in the stack of open elements, but the element is not in scope, then this is a parse error; return. + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->node_name ) ) { + return; } - if ( self::is_special( $item ) ) { - $furthest_block = $item; - break; + /* + * > Let furthest block be the topmost node in the stack of open elements that is lower in the stack + * > than formatting element, and is an element in the special category. There might not be one. + */ + $is_above_formatting_element = true; + $furthest_block = null; + foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) { + if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) { + continue; + } + + if ( $is_above_formatting_element ) { + $is_above_formatting_element = false; + continue; + } + + if ( self::is_special( $item ) ) { + $furthest_block = $item; + break; + } } - } - /* - * > If there is no furthest block, then the UA must first pop all the nodes from the bottom of the - * > stack of open elements, from the current node up to and including formatting element, then - * > remove formatting element from the list of active formatting elements, and finally return. - */ - if ( null === $furthest_block ) { - foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { - $this->state->stack_of_open_elements->pop(); + /* + * > If there is no furthest block, then the UA must first pop all the nodes from the bottom of the + * > stack of open elements, from the current node up to and including formatting element, then + * > remove formatting element from the list of active formatting elements, and finally return. + */ + if ( null === $furthest_block ) { + foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { + $this->state->stack_of_open_elements->pop(); - if ( $formatting_element->bookmark_name === $item->bookmark_name ) { - $this->state->active_formatting_elements->remove_node( $formatting_element ); - return; + if ( $formatting_element->bookmark_name === $item->bookmark_name ) { + $this->state->active_formatting_elements->remove_node( $formatting_element ); + return; + } } } + + $this->bail( 'Cannot extract common ancestor in adoption agency algorithm.' ); } - $this->bail( 'Cannot extract common ancestor in adoption agency algorithm.' ); + $this->bail( 'Cannot run adoption agency when looping required.' ); } /** diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 1266b6472ec1e..2282799c3903d 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 30 + count: 31 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' From 464079d8f6f579b0a633ea1c1a863e7bfc06f347 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Oct 2026 22:49:26 -0700 Subject: [PATCH 3/9] Remove break after goto to fix deadCode.unreachable error --- src/wp-includes/html-api/class-wp-html-processor.php | 1 - tests/phpstan/baselines/deadCode.unreachable.neon | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index c278cca8b0e23..cfaffa720d4a9 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1585,7 +1585,6 @@ private function step_initial(): bool { return $this->step(); } goto initial_anything_else; - break; /* * > A comment token diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 2282799c3903d..1266b6472ec1e 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 31 + count: 30 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' From 88e05ddbb8f4bb474a02e757ccf6a4cb2599d55c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 8 Oct 2026 09:19:43 -0700 Subject: [PATCH 4/9] Remove additional breaks after gotos --- src/wp-includes/html-api/class-wp-html-processor.php | 12 ------------ tests/phpstan/baselines/deadCode.unreachable.neon | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index cfaffa720d4a9..cc9ebb5757cbe 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1677,7 +1677,6 @@ private function step_before_html(): bool { return $this->step(); } goto before_html_anything_else; - break; /* * > A start tag whose tag name is "html" @@ -1699,7 +1698,6 @@ private function step_before_html(): bool { * > Act as described in the "anything else" entry below. */ goto before_html_anything_else; - break; } /* @@ -1759,7 +1757,6 @@ private function step_before_head(): bool { return $this->step(); } goto before_head_anything_else; - break; /* * > A comment token @@ -1804,7 +1801,6 @@ private function step_before_head(): bool { case '-BODY': case '-HTML': goto before_head_anything_else; - break; } if ( $is_closer ) { @@ -1860,7 +1856,6 @@ private function step_in_head(): bool { } goto in_head_anything_else; - break; /* * > A comment token @@ -1992,7 +1987,6 @@ private function step_in_head(): bool { * > Act as described in the "anything else" entry below. */ goto in_head_anything_else; - break; /* * > A start tag whose tag name is "template" @@ -2085,7 +2079,6 @@ private function step_in_head_noscript(): bool { } goto in_head_noscript_anything_else; - break; /* * > A DOCTYPE token @@ -2189,7 +2182,6 @@ private function step_after_head(): bool { return true; } goto after_head_anything_else; - break; /* * > A comment token @@ -2278,7 +2270,6 @@ private function step_after_head(): bool { * > Act as described in the "anything else" entry below. */ goto after_head_anything_else; - break; } /* @@ -3820,7 +3811,6 @@ private function step_in_column_group(): bool { } goto in_column_group_anything_else; - break; /* * > A comment token @@ -4381,7 +4371,6 @@ private function step_after_body(): bool { return $this->step_in_body(); } goto after_body_anything_else; - break; /* * > A comment token @@ -4704,7 +4693,6 @@ private function step_after_after_body(): bool { return $this->step_in_body(); } goto after_after_body_anything_else; - break; } /* diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 1266b6472ec1e..162b2d35e474f 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 30 + count: 18 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' From f7fc23609430fc5f32911195639ea427f3bc54e7 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 8 Oct 2026 09:23:51 -0700 Subject: [PATCH 5/9] Remove unnecessary returns now covered by bail returning never --- src/wp-includes/html-api/class-wp-html-processor.php | 8 -------- tests/phpstan/baselines/deadCode.unreachable.neon | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index cc9ebb5757cbe..8bc5a99fcbacf 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -3358,8 +3358,6 @@ private function step_in_body(): bool { } $this->bail( 'Should not have been able to reach end of IN BODY processing. Check HTML API code.' ); - // This unnecessary return prevents tools from inaccurately reporting type errors. - return false; } /** @@ -3408,8 +3406,6 @@ private function in_body_any_other_end_tag(): bool { } $this->bail( 'Should not have been able to reach end of "any other end tag" IN BODY processing. Check HTML API code.' ); - // This unnecessary return prevents tools from inaccurately reporting type errors. - return false; } /** @@ -5086,8 +5082,6 @@ private function step_in_foreign_content(): bool { } $this->bail( 'Should not have been able to reach end of IN FOREIGN CONTENT processing. Check HTML API code.' ); - // This unnecessary return prevents tools from inaccurately reporting type errors. - return false; } /* @@ -6375,8 +6369,6 @@ private function is_html_integration_point(): bool { } $this->bail( 'Should not have reached end of HTML Integration Point detection: check HTML API code.' ); - // This unnecessary return prevents tools from inaccurately reporting type errors. - return false; } /** diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 162b2d35e474f..3a88a0dc31b76 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 18 + count: 16 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' From 83302d9594f1113c4c376a2f233381975ae89237 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 8 Oct 2026 09:33:33 -0700 Subject: [PATCH 6/9] Remove remaining unreachable statement which PHPStan protects against --- src/wp-includes/html-api/class-wp-html-processor.php | 2 -- tests/phpstan/baselines/deadCode.unreachable.neon | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 8bc5a99fcbacf..fdd162780a8c5 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -3356,8 +3356,6 @@ private function step_in_body(): bool { */ return $this->in_body_any_other_end_tag(); } - - $this->bail( 'Should not have been able to reach end of IN BODY processing. Check HTML API code.' ); } /** diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 3a88a0dc31b76..a4c976aafa442 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 16 + count: 15 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' From f06d4a68bd065023a67d95163112fed491f8d023 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 8 Oct 2026 09:36:20 -0700 Subject: [PATCH 7/9] Remove one last unreachable break statement inside of an if --- src/wp-includes/html-api/class-wp-html-processor.php | 1 - tests/phpstan/baselines/deadCode.unreachable.neon | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index fdd162780a8c5..ed7ef49b0253c 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -3480,7 +3480,6 @@ private function step_in_table(): bool { // Non-whitespace would trigger fostering, unsupported at this time. $this->bail( 'Foster parenting is not supported.' ); - break; } break; diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index a4c976aafa442..4be6c0c363686 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 15 + count: 14 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' From c9ecea45e9f1cc88a61077873dbf0ebf957a0a3b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 8 Oct 2026 21:23:00 -0700 Subject: [PATCH 8/9] Remove else statement which allows removal of break for phpcs --- .../html-api/class-wp-html-processor.php | 35 +++++++++---------- .../baselines/deadCode.unreachable.neon | 2 +- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index ed7ef49b0253c..5fb724c49043d 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -2773,28 +2773,27 @@ private function step_in_body(): bool { $this->state->stack_of_open_elements->remove_node( $node ); return true; - } else { - /* - * > If the stack of open elements does not have a form element in scope, - * > then this is a parse error; return and ignore the token. - * - * Note that unlike in the clause above, this is checking for any FORM in scope. - */ - if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' ) ) { - // Parse error: ignore the token. - return $this->step(); - } + } - $this->generate_implied_end_tags(); + /* + * > If the stack of open elements does not have a form element in scope, + * > then this is a parse error; return and ignore the token. + * + * Note that unlike in the clause above, this is checking for any FORM in scope. + */ + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' ) ) { + // Parse error: ignore the token. + return $this->step(); + } - if ( ! $this->state->stack_of_open_elements->current_node_is( 'FORM' ) ) { - // @todo Indicate a parse error once it's possible. This error does not impact the logic here. - } + $this->generate_implied_end_tags(); - $this->state->stack_of_open_elements->pop_until( 'FORM' ); - return true; + if ( ! $this->state->stack_of_open_elements->current_node_is( 'FORM' ) ) { + // @todo Indicate a parse error once it's possible. This error does not impact the logic here. } - break; + + $this->state->stack_of_open_elements->pop_until( 'FORM' ); + return true; /* * > An end tag whose tag name is "p" diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 4be6c0c363686..bf61e95da6829 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 14 + count: 13 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' From 0f13b8511d269dba513dbda6dd9ed012eb093051 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 8 Oct 2026 21:34:26 -0700 Subject: [PATCH 9/9] Move is_tag_closer conditions into else which fixes deadCode.unreachable and if.alwaysTrue errors --- .../html-api/class-wp-html-processor.php | 26 ++++++++----------- .../baselines/booleanAnd.leftAlwaysTrue.neon | 5 ---- .../baselines/deadCode.unreachable.neon | 2 +- tests/phpstan/baselines/if.alwaysTrue.neon | 2 +- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 5fb724c49043d..9eceb18729c1f 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -4963,20 +4963,18 @@ private function step_in_foreign_content(): bool { $this->state->stack_of_open_elements->pop(); } return true; - } - - /* - * > An end tag whose name is "script", if the current node is an SVG script element. - */ - if ( $this->is_tag_closer() && 'SCRIPT' === $this->state->current_token->node_name && 'svg' === $this->state->current_token->namespace ) { - $this->state->stack_of_open_elements->pop(); - return true; - } + } else { + /* + * > An end tag whose name is "script", if the current node is an SVG script element. + */ + if ( 'SCRIPT' === $this->state->current_token->node_name && 'svg' === $this->state->current_token->namespace ) { + $this->state->stack_of_open_elements->pop(); + return true; + } - /* - * > Any other end tag - */ - if ( $this->is_tag_closer() ) { + /* + * > Any other end tag + */ $node = $this->state->stack_of_open_elements->current_node(); if ( $tag_name !== $node->node_name ) { // @todo Indicate a parse error once it's possible. @@ -5076,8 +5074,6 @@ private function step_in_foreign_content(): bool { $this->bail( "Unaware of the requested parsing mode: '{$this->state->insertion_mode}'." ); } } - - $this->bail( 'Should not have been able to reach end of IN FOREIGN CONTENT processing. Check HTML API code.' ); } /* diff --git a/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon b/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon index 69e4b48b16039..2ea5a45c3cfc2 100644 --- a/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon +++ b/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon @@ -33,8 +33,3 @@ parameters: identifier: booleanAnd.leftAlwaysTrue count: 1 path: ../../../src/wp-includes/canonical.php - - - message: '#^Left side of && is always true\.$#' - identifier: booleanAnd.leftAlwaysTrue - count: 1 - path: ../../../src/wp-includes/html-api/class-wp-html-processor.php diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index bf61e95da6829..22f5a17da9286 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -56,7 +56,7 @@ parameters: - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable - count: 13 + count: 12 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php - message: '#^Unreachable statement \- code above always terminates\.$#' diff --git a/tests/phpstan/baselines/if.alwaysTrue.neon b/tests/phpstan/baselines/if.alwaysTrue.neon index 6888b9780b5f9..ffd7d9081b25f 100644 --- a/tests/phpstan/baselines/if.alwaysTrue.neon +++ b/tests/phpstan/baselines/if.alwaysTrue.neon @@ -46,5 +46,5 @@ parameters: - message: '#^If condition is always true\.$#' identifier: if.alwaysTrue - count: 2 + count: 1 path: ../../../src/wp-includes/html-api/class-wp-html-processor.php