@@ -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 */
4853function 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 */
205218function 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 */
497495function 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]*+<!-- wp-parser-code-snippet-placeholder:[0-9]+ -->)+[ \t\n]*+)</code></pre># ' ,
12481252 function ( $ matches ) {
1249- preg_match_all (
1250- '/<!-- wp-parser-code-snippet-placeholder:([0-9]+) -->/ ' ,
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 ( '< ' , '> ' ), array ( '< ' , '> ' ), trim ( $ matches [1 ] ) );
1254+ return preg_replace ( '/[ \t\n]++(?=<!-- wp-parser-code-snippet-placeholder:)/ ' , "\n" , $ placeholders );
12601255 },
12611256 $ description
12621257 );
0 commit comments