Skip to content

Commit 48099d1

Browse files
committed
Reduce snippet parser duplication
1 parent 7f95811 commit 48099d1

4 files changed

Lines changed: 200 additions & 365 deletions

File tree

lib/class-command.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ public function export( $args ) {
3434
}
3535

3636
/**
37-
* Read a JSON file containing the PHPDoc markup, convert it into WordPress posts, and insert into DB.
37+
* Imports an exported parser document into WordPress.
38+
*
39+
* The command validates the parsed-file envelope before invoking the importer
40+
* and preserves setup Blueprint object and list shapes while decoding JSON.
3841
*
3942
* @synopsis <file> [--quick] [--import-internal]
4043
*

lib/class-file-reflector.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,28 @@ class File_Reflector extends FileReflector {
4545
protected $last_doc = null;
4646

4747
/**
48-
* Whether the file DocBlock was parsed with its complete fence bodies blanked.
48+
* Whether complete fence bodies were blanked before parsing the file DocBlock.
4949
*
5050
* @var bool
5151
*/
5252
protected $docblock_was_sanitized = false;
5353

5454
/**
55-
* Reports whether false in-fence tags were absent from the parsed DocBlock.
55+
* Indicates whether complete file-DocBlock fence bodies were blanked.
5656
*
57-
* @return bool
57+
* @return bool Whether phpDocumentor parsed a sanitized comment.
5858
*/
5959
public function wasDocBlockSanitized() {
6060
return $this->docblock_was_sanitized;
6161
}
6262

6363
/**
64-
* Let phpDocumentor identify a file DocBlock without parsing fenced PHP as tags.
64+
* Lets phpDocumentor identify a file DocBlock without parsing fenced PHP as tags.
6565
*
66-
* FileReflector parses and removes the file comment before visiting individual
67-
* nodes. Temporarily blanking complete fence bodies preserves that lifecycle;
68-
* export_docblock() later recovers the untouched source from the file contents.
66+
* `FileReflector` consumes the file comment before visiting its nodes, so
67+
* `export_docblock()` cannot sanitize it later. Complete fence bodies are
68+
* temporarily blanked for the parent traversal and restored afterward; the
69+
* untouched file contents remain available for snippet extraction.
6970
*
7071
* @param PHPParser_Node[] $nodes
7172
*

lib/runner.php

Lines changed: 53 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ function get_wp_files( $directory ) {
4040
}
4141

4242
/**
43-
* @param array $files
44-
* @param string $root
43+
* Parses PHP files into records consumed by the importer.
4544
*
46-
* @return array
45+
* Setup Blueprints flow from file and class DocBlocks to descendants. A
46+
* descendant copies only definitions referenced by one of its snippets.
47+
*
48+
* @param string[] $files PHP source files to parse.
49+
* @param string $root Root path removed from exported file paths.
50+
*
51+
* @return array Parsed file records in input order.
4752
*/
4853
function parse_files( $files, $root ) {
4954
$output = array();
@@ -196,11 +201,19 @@ function ( $matches ) use ( $replacement_string ) {
196201
}
197202

198203
/**
199-
* @param BaseReflector|ReflectionAbstract $element
200-
* @param array $inherited_setup_blueprints Optional. Setup Blueprints inherited from the file or class DocBlock.
201-
* @param string $source_file Optional. Source path used in invalid snippet metadata errors.
204+
* Exports one reflected DocBlock and its runnable snippet metadata.
202205
*
203-
* @return array
206+
* Fenced descriptions are recovered from source because phpDocumentor may
207+
* interpret PHP lines beginning with `@` as tags. Named setup references may
208+
* resolve against definitions inherited from the enclosing file or class.
209+
*
210+
* @param BaseReflector|ReflectionAbstract $element Reflected DocBlock owner.
211+
* @param array $inherited_setup_blueprints Setup Blueprints visible from enclosing scopes.
212+
* @param string $source_file Source path used in metadata errors.
213+
*
214+
* @throws \InvalidArgumentException When snippet metadata is invalid or ambiguous.
215+
*
216+
* @return array Exported descriptions, tags, snippets, and referenced setup Blueprints.
204217
*/
205218
function export_docblock( $element, array $inherited_setup_blueprints = array(), $source_file = '' ) {
206219
$node_docblock = null;
@@ -291,53 +304,38 @@ function export_docblock( $element, array $inherited_setup_blueprints = array(),
291304
$source_lines[ $key ] = preg_replace( '/^[ \t]*\*[ \t]?/', '', $source_line );
292305
}
293306

294-
$description_lines = array();
295-
$closing_pattern = null;
296-
$open_fence_tag_count = null;
297-
$first_open_fence_tag_line = null;
307+
// Reuse tokenizer boundaries so source recovery and snippet export agree
308+
// on which exact backtick runs delimit complete fences.
309+
$source_fences = tokenize_docblock_code_fences( implode( "\n", $source_lines ) );
310+
$source_fence_index = 0;
311+
$description_lines = array();
298312
// phpDocumentor starts its tag block at an optionally indented @ followed
299313
// by a letter. Once started, only a column-zero @ with any valid tag name
300314
// opens another tag; indented lines extend the preceding tag instead.
301-
$parsed_tag_block_started = false;
302-
foreach ( $source_lines as $source_line ) {
303-
if ( null === $closing_pattern ) {
304-
if ( preg_match( '/^[ \t]*(`{3,})[^`]*$/', $source_line, $opening ) ) {
305-
$closing_pattern = '/^[ \t]*' . preg_quote( $opening[1], '/' ) . '[ \t]*$/';
306-
$open_fence_tag_count = count( $fenced_docblock_tag_names );
307-
$first_open_fence_tag_line = null;
308-
} elseif (
309-
( ! $parsed_tag_block_started && preg_match( '/^[ \t]*@\pL/u', $source_line ) ) ||
310-
( $parsed_tag_block_started && preg_match( '/^@[\w\-_\\\\]+/u', $source_line ) )
311-
) {
315+
$parsed_tag_block_started = false;
316+
foreach ( $source_lines as $source_line_number => $source_line ) {
317+
while (
318+
isset( $source_fences[ $source_fence_index ] ) &&
319+
$source_line_number >= $source_fences[ $source_fence_index ]['end']
320+
) {
321+
$source_fence_index++;
322+
}
323+
$is_in_fence = isset( $source_fences[ $source_fence_index ] ) &&
324+
$source_line_number > $source_fences[ $source_fence_index ]['start'];
325+
326+
$tag_pattern = $parsed_tag_block_started
327+
? '/^@([\w\-_\\\\]+)/u'
328+
: '/^[ \t]*@([\pL][\w\-_\\\\]*)/u';
329+
if ( preg_match( $tag_pattern, $source_line, $tag_match ) ) {
330+
if ( ! $is_in_fence ) {
312331
break;
313332
}
314-
} elseif ( preg_match( $closing_pattern, $source_line ) ) {
315-
$closing_pattern = null;
316-
$open_fence_tag_count = null;
317-
$first_open_fence_tag_line = null;
318-
} else {
319-
$tag_name = null;
320-
if ( ! $parsed_tag_block_started && preg_match( '/^[ \t]*@([\pL][\w\-_\\\\]*)/u', $source_line, $tag_match ) ) {
321-
$parsed_tag_block_started = true;
322-
$tag_name = $tag_match[1];
323-
} elseif ( $parsed_tag_block_started && preg_match( '/^@([\w\-_\\\\]+)/u', $source_line, $tag_match ) ) {
324-
$tag_name = $tag_match[1];
325-
}
326-
327-
if ( null !== $tag_name ) {
328-
if ( null === $first_open_fence_tag_line ) {
329-
$first_open_fence_tag_line = count( $description_lines );
330-
}
331-
$fenced_docblock_tag_names[] = $tag_name;
332-
}
333+
$parsed_tag_block_started = true;
334+
$fenced_docblock_tag_names[] = $tag_match[1];
333335
}
334336

335337
$description_lines[] = $source_line;
336338
}
337-
if ( null !== $closing_pattern && null !== $first_open_fence_tag_line ) {
338-
$description_lines = array_slice( $description_lines, 0, $first_open_fence_tag_line );
339-
$fenced_docblock_tag_names = array_slice( $fenced_docblock_tag_names, 0, $open_fence_tag_count );
340-
}
341339
// Remove blank wrapper lines without stripping indentation from a fence
342340
// that begins or ends the description. That indentation controls both
343341
// content dedenting and the fence's Markdown nesting level.
@@ -496,11 +494,18 @@ function export_docblock( $element, array $inherited_setup_blueprints = array(),
496494
*/
497495
function sanitize_docblock_fenced_contents( $source_docblock ) {
498496
$original_source_docblock = $source_docblock;
497+
498+
// Normalize line endings so tokenizer indexes map to physical source lines.
499499
$source_docblock = preg_replace( "/\r\n?/", "\n", $source_docblock );
500+
501+
// Remove the opener and at most one decorative whitespace byte.
500502
$contents = preg_replace( '/\A[ \t]*\/\*\*[ \t]?/', '', $source_docblock );
503+
504+
// Remove only the end-anchored closing delimiter and its indentation.
501505
$contents = preg_replace( '/[ \t]*\*\/[ \t]*\z/', '', $contents );
502506
$content_lines = explode( "\n", $contents );
503507
foreach ( $content_lines as $key => $line ) {
508+
// Remove the decorative star and at most one following whitespace byte.
504509
$content_lines[ $key ] = preg_replace( '/^[ \t]*\*[ \t]?/', '', $line );
505510
}
506511

@@ -512,8 +517,7 @@ function sanitize_docblock_fenced_contents( $source_docblock ) {
512517
$source_lines = explode( "\n", $source_docblock );
513518
foreach ( $fences as $fence ) {
514519
for ( $line = $fence['start'] + 1; $line < $fence['end']; $line++ ) {
515-
// Retain the DocBlock's decorative `*`, but no text that phpDocumentor
516-
// could reinterpret as a tag or part of the surrounding description.
520+
// Retain indentation and the decorative star while blanking body text.
517521
$source_lines[ $line ] = preg_match( '/^([ \t]*\*)/', $source_lines[ $line ], $prefix ) ? $prefix[1] : '';
518522
}
519523
}
@@ -1246,17 +1250,8 @@ function format_long_description( $description ) {
12461250
$description = preg_replace_callback(
12471251
'#<pre><code>((?:[ \t\n]*+&lt;!-- wp-parser-code-snippet-placeholder:[0-9]+ --&gt;)+[ \t\n]*+)</code></pre>#',
12481252
function ( $matches ) {
1249-
preg_match_all(
1250-
'/&lt;!-- wp-parser-code-snippet-placeholder:([0-9]+) --&gt;/',
1251-
$matches[1],
1252-
$placeholder_matches
1253-
);
1254-
$placeholders = array();
1255-
foreach ( $placeholder_matches[1] as $index ) {
1256-
$placeholders[] = '<!-- wp-parser-code-snippet-placeholder:' . $index . ' -->';
1257-
}
1258-
1259-
return implode( "\n", $placeholders );
1253+
$placeholders = str_replace( array( '&lt;', '&gt;' ), array( '<', '>' ), trim( $matches[1] ) );
1254+
return preg_replace( '/[ \t\n]++(?=<!-- wp-parser-code-snippet-placeholder:)/', "\n", $placeholders );
12601255
},
12611256
$description
12621257
);

0 commit comments

Comments
 (0)