Repository navigation
Conversation
`WP_Block_Processor::extract_full_block_and_advance()` stopped on the token after a void block, but on the last token of every other block. The next advance then skipped that token: a `next_block()` loop dropped the block or freeform HTML after a top-level void block, and extraction left the block after an inner void block out of the result. Extraction now stops on the last token of the extracted block in every case. Remove the check for an HTML span after an inner extraction: it handled the old void case and copied inner HTML at the end of incomplete input into the parent block. Update the docblocks to state where extraction stops. See #66138.
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 class docblock's depth walk continued while `get_depth() > $depth`, so it stopped on the closing delimiter of the first inner block. Starting on `gallery` in `<!-- wp:gallery --><!-- wp:image --><!-- /wp:image --><!-- /wp:gallery -->`, it stopped on `<!-- /wp:image -->` with the gallery still open. Continue while `get_depth() >= $depth` so the walk stops on the block's own closing delimiter. See #66138.
When the input ends in a partial delimiter, `extract_full_block_and_advance()` can leave the processor on the final HTML span, and the extracted block can omit content at the end of the document. The docblock said `get_span()` returns `null` whenever the document ends before the block closes. Qualify that statement for incomplete input, and return the original content from both splice examples when `get_last_error()` reports an error. Without the check, the examples rewrite `<!-- wp:gallery -->y<!--` as `<!-- wp:gallery /-->`. The incomplete-input test now asserts the error, the stop position, and that the starting block was found. See #66138.
Add the missing semicolon after continue so the documented example compiles. See #66138.
dmsnell
left a comment
There was a problem hiding this comment.
@sirreal would you mind speaking the change of intentional behavior, where the _and_advance() is communicated that this skips past the open block.
I’m not saying it’s wrong, but I think it’s changing the contract rather than fixing the defect (that this doesn't advance past open blocks).
I think the root cause is that once we reach the closing block delimiter, we are past the open block; it’s closed before we pause at it.
granted, I think the spirit of this PR upholds that; the goal if I remember was to be able to call next_token() immediately after extract_full_block_and_advance(), but I’m kind of surprised at the scope of the fix here, thinking it shouldn’t be this big.
I wonder if we do something like advance to a zero-width HTML span.
also, I think the current guidance is that one ought not call extract_full_block_and_advance() on VOID block delimiters. that may not be great, but allocate_and_parse_attributes() gives everything and there’s no need to do more.
perhaps the best realization of this fix is simply to remain at the same spot on a VOID block delimiter after extracting it.
either way I think we need to be careful here to be intentional about the fix. this is already too confusing as is. thank you!
| * $after_gallery = $processor->get_span()->start; | ||
| * if ( null !== $processor->get_last_error() ) { | ||
| * return $post_content; | ||
| * } |
There was a problem hiding this comment.
for this and above, I wonder if this level of error-checking is more distracting than helpful.
| * them in place. | ||
| * | ||
| * Once this function returns, the parser will be matched on token following the close | ||
| * of the given block. |
There was a problem hiding this comment.
this has always been confusing to me, but this PR seems to be changing behavior, claiming that VOID block delimiters are wrong, but according to this old description, the VOID block delimiters are the only ones that are right
Extracting a void block again stops on the token after it, as in 6.9, instead of on its delimiter. Blocks with inner content still stop on their closing delimiter, and freeform HTML on its span. Extraction skipped the token after an inner void block, because the inner extraction had already advanced to it and the loop advanced again. The loop now does not advance after extracting an inner void block. A group's inner blocks include every block after a void inner block, and extraction of a group that ends in a void inner block stops on the group's closing delimiter. A `next_block()` loop still skips the token after a top-level void block, so those cases are removed from the `parse_blocks()` comparison. The splice examples take the end of a void block from its own delimiter. See #66138.
Add stop-position cases for a block whose input ends after a void inner block: at the end of the document, and before incomplete input. In both, the loop does not advance after the inner void extraction, and must still stop. See #66138.
Revert this branch's changes to the class and method docblocks, their examples, and the inline comment on the advance flag. The comment block for the removed HTML check after recursive extraction stays removed with that check. No code changes. See #66138.
Add `void` return types to the two new tests, and the `array` return type and row shape to their data provider. See #66138.
|
Good feedback, thank you. The behavior on void blocks could probably use an example of how to loop properly, but I agree that we don't want to change behavior or contracts. I've reworked this to be a minimal change only fixing the defect and not changing any public behaviors. |
|
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:
|
|
| Location | Error | Identifier | First reported |
|---|---|---|---|
src/wp-includes/class-wp-block-processor.php:1300 |
Method WP_Block_Processor::extract_full_block_and_advance() should return array<array>|null but returns array<string, list<string|null>|string|null>. |
return.type |
2f580c0 |
src/wp-includes/class-wp-block-processor.php:1318 |
Method WP_Block_Processor::extract_full_block_and_advance() should return array<array>|null but returns array<string, array|string|null>. |
return.type |
6dea7ce |
src/wp-includes/class-wp-block-processor.php:1343 |
Method WP_Block_Processor::extract_full_block_and_advance() should return array<array>|null but returns array<string, array|string|null>. |
return.type |
2f580c0 |
Resolved errors (2)
| Location | Error | Identifier | First reported | Resolved |
|---|---|---|---|---|
src/wp-includes/class-wp-block-processor.php:1318 |
Ignored error pattern #^Method WP_Block_Processor\:\:extract_full_block_and_advance\(\) should return array\<array\>\|null but returns array\<string, array\|string\|null\>\.$# (return.type) in path /home/runner/work/wordpress-develop/wordpress-develop/src/wp-includes/class-wp-block-processor.php is expected to occur 1 time, but occurred 2 times. |
ignore.count |
6dea7ce | 2f580c0 |
src/wp-includes/class-wp-block-processor.php:1330 |
Negated boolean expression is always true. |
booleanNot.alwaysTrue |
93aada3 | 257d291 |
Running PHPStan locally
To run the analysis on your own machine, use npm run typecheck:php, or composer phpstan outside the Docker environment.
|
@sirreal I pushed 93aada3 to this branch after spending some time trying to understand the fix, and I think I finally have a way to express what the expectation should be for this function. I flipped the flag so that we’re making a positive check rather than negating one. initially I renamed Then I realized I had the condition backwards in my head. We’re not speaking about the closer so much as we’re asking if we are already where we need to be for the next loop iteration, and short-circuiting Maybe we could still rename this Then I didn’t like the split assignments throughout the loop so I lifted it to the first line. Now it only needs a single assignment and is hopefully more difficult to miss in any follow-up work. |
dmsnell
left a comment
There was a problem hiding this comment.
@sirreal you can revert my change or leave it. I have reviewed this and consider it good to go; best to get it in for WordPress 7.2
@westonruter thanks for the impure patch. I am so sad every time I see that, but I don’t want to hold up this patch on account of it.
|
oops: I found another failing case by trying to use this for |
Remove redundant wrapper checks and repeated recovery-mode cases. Strengthen mail assertions to check observable message content. Developed in: WordPress#15023 Props huzaifaalmesbah. See #65819. git-svn-id: https://develop.svn.wordpress.org/trunk@64983 602fd350-edb4-49c9-b593-d223f7449a82
|
@sirreal I returned to my previous comment and updated the tests you added, which brings the behaviors back to something we discussed, but was awash in the evolution of this patch the way it stands now, the class is designed to support this exact flow public function parse( $document ) {
$this->document = $document;
$this->offset = 0;
$this->output = array();
$this->stack = array();
$this->block_processor = new WP_Block_Processor( $document );
while ( $this->block_processor->next_block( '*' ) ) {
$this->output[] = $this->block_processor->extract_full_block_and_advance();
}
return $this->output;
}and I have this passing all tests locally when I replace the |
When the Block Processor was introduced, the `extract_full_block_and_advance()` method was provided in order to support backwards compatibility with `parse_blocks()`, specifically when wanting to extract the structural representation of a subset of blocks within a large post. Unfortunately, when extracting void blocks, it was advancing to the next token, which, if called from within a loop that was already advancing, would lead to a skipped block delimiter. In this patch the behavior is normalized such that, after a call to `extract_full_block_and_advance()`, the calling code should call `next_token()` to continue processing the next block or top-level freeform HTML in the post. Developed in: #14104 Discussed in: https://core.trac.wordpress.org/ticket/66138 Follow-up to r60939. Props dmsnell, jonsurrell, westonruter. See #66138. git-svn-id: https://develop.svn.wordpress.org/trunk@64994 602fd350-edb4-49c9-b593-d223f7449a82
When the Block Processor was introduced, the `extract_full_block_and_advance()` method was provided in order to support backwards compatibility with `parse_blocks()`, specifically when wanting to extract the structural representation of a subset of blocks within a large post. Unfortunately, when extracting void blocks, it was advancing to the next token, which, if called from within a loop that was already advancing, would lead to a skipped block delimiter. In this patch the behavior is normalized such that, after a call to `extract_full_block_and_advance()`, the calling code should call `next_token()` to continue processing the next block or top-level freeform HTML in the post. Developed in: WordPress/wordpress-develop#14104 Discussed in: https://core.trac.wordpress.org/ticket/66138 Follow-up to r60939. Props dmsnell, jonsurrell, westonruter. See #66138. Built from https://develop.svn.wordpress.org/trunk@64994 git-svn-id: http://core.svn.wordpress.org/trunk@64144 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Trac ticket: Core-66138.
Follow-up to r60939.
WP_Block_Processor::extract_full_block_and_advance()advances past void blocks but stops on the closing delimiter of other blocks. A subsequentnext_block()therefore skips the block after a void block:$html<!-- wp:a --><!-- /wp:a --><!-- wp:b /-->core/a,core/b<!-- wp:a /--><!-- wp:b /-->core/aAt the top level, a caller can work around this: after extracting a void block, the processor is already on the next token. Nested extraction has no workaround. The method's own loop advances past the token after an inner void block, so the returned tree drops it:
$htmlg, before<!-- wp:g --><!-- wp:v --><!-- /wp:v --><!-- wp:w /--><!-- /wp:g -->core/v,core/wcore/v,core/w<!-- wp:g --><!-- wp:v /--><!-- wp:w /--><!-- /wp:g -->core/vcore/v,core/wThe loop no longer advances after extracting an inner void block. Extraction still stops on the closing delimiter, on the freeform HTML span, or on the token after a void block. A block whose last inner block is void now follows that rule; before, it stopped on the token after its closing delimiter. The top-level
next_block()loop is unchanged. The redundant HTML check after recursive extraction is removed, so incomplete input no longer copies an inner block's trailing HTML into its parent.Tests cover nested void blocks, incomplete input, and the stop position for each kind of block. 5 of the 14 new cases fail on trunk.
Trac ticket: https://core.trac.wordpress.org/ticket/66138
Use of AI Tools
AI assistance: Yes
Tools: Claude Code, Codex
Models: Claude Opus 5.5, GPT-6
Used for: Investigation, implementation, tests, and description editing. Codex reviewed the diff and edited the description.
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.