Skip to content

Preserve documentation and hook string semantics - #262

Draft
sirreal wants to merge 13 commits into
masterfrom
fix-254
Draft

Preserve documentation and hook string semantics#262
sirreal wants to merge 13 commits into
masterfrom
fix-254

Conversation

@sirreal

@sirreal sirreal commented Jul 23, 2026

Copy link
Copy Markdown
Member

Fixes #254.

What changed

  • Stop applying namespace cleanup to every exported string.
  • Normalize AST names at expression and metadata boundaries, preserving established JSON for defaults, constants, references, and call metadata.
  • Export literal hook names with PHP string semantics.

Why

The PHP-Parser 5 compatibility cleanup treated documentation and hook literals as identifiers. It removed meaningful backslashes. Simply deleting that cleanup also caused broad JSON churn such as false becoming \false.

Keep syntax normalization where it belongs. Leave text alone.

Testing

  • npm run test:phpunit — 28 tests, 158 assertions; 11 pre-existing PHPUnit deprecation warnings.
  • php tests/prep-diff-test.php
  • Full wp-includes export: expected documentation escapes remain; no stray global prefixes in defaults, constant values, references, or call-class metadata.

sirreal added 13 commits August 14, 2026 11:44
`Hook_Reflector::getName()` short-circuited on `Scalar\String_` nodes and
returned the interpreted string value. Escape sequences were therefore
resolved, so `do_action( "\x09tab" )` exported a literal tab and
`do_action( "\xC0 bad" )` exported a raw 0xC0 byte. That byte is not valid
UTF-8, `json_encode()` returns `false` for it, and `wp parser export`
silently produced no JSON at all for the whole run.

Route string nodes through `Pretty_Printer` like every other expression.
The printer returns php-parser's `rawValue` attribute, which is the
source-verbatim spelling, and `cleanupName()` strips the quotes.

Also check `json_encode()` for failure in `Command::_get_phpdoc_data()` and
fail loudly with `json_last_error_msg()` instead of writing an empty file.
The pretty printer inherits an override that returns PHP-Parser's `rawValue`
attribute so escape sequences are not interpreted. PHP-Parser sets that
attribute to the body of a doc string, without the delimiters, so
`apply_filters( 'f', <<<EOT ... EOT, 2 )` exported its argument as a bare
`body` string with embedded newlines instead of PHP source.

Print heredoc and nowdoc nodes with the default printer, which reproduces
the `<<<LABEL ... LABEL` form and does not interpret escape sequences in doc
strings either.
`pName_FullyQualified()` prints single-segment fully-qualified names without
the leading backslash regardless of namespace context, so inside a namespaced
file the printed form denotes a namespaced symbol rather than the global one.
This is an accepted limitation because the parser targets global-namespace
WordPress core code.
The global namespace prefixes are stripped from inline `{@link}` and
`{@see}` references after the DocBlock text has been rendered, so the
stripping also reached into rendered code regions and silently deleted
the backslash an author had written in a verbatim code sample.

Carve out `<code>` regions before stripping, the same way `fix_newlines()`
protects the newlines in those regions, so code samples are exported as
they were written.
The quote-stripping pattern required a body free of quote characters, so
a hook name that contained one, like `do_action( "it's" );`, was exported
with the quotes that surround it in the source.

Match the opening quote and require the same quote at the end, allowing
the body to hold the other quote character or an escaped copy of the
delimiter. Only that pair is stripped; the body keeps its source spelling,
so `do_action( 'it\'s' );` exports as `it\'s`. Concatenated expressions
still fall through to the dynamic-name handling below.
`Method_Call_Reflector::_getClassMapping()` maps a handful of WordPress factory
functions to the class they return, so that `get_current_screen()->add_help_tab()`
is exported as a use of `WP_Screen::add_help_tab()`. The lookup never matched
before this branch, because the printed receiver carried a leading backslash
that the mapping keys do not have. Pin the restored behavior with a test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Some characters are stripped from documentation

1 participant