Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion lib/runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ function export_docblock_code_snippets( $text, &$setup_blueprints = null, $fence

$snippet = array(
'type' => 'php-code-snippet',
'code' => $fences[ $i ]['code'],
'code' => add_docblock_php_snippet_preamble( $fences[ $i ]['code'] ),
);

if ( null !== $fences[ $i ]['referenced_setup'] ) {
Expand Down Expand Up @@ -1085,6 +1085,33 @@ function export_docblock_code_snippets( $text, &$setup_blueprints = null, $fence
return $snippets;
}

/**
* Adds the WordPress bootstrap preamble to an interactive PHP snippet.
*
* Interactive snippets run in a standalone PHP process, so they need to load
* WordPress before executing the example. Authors may omit the boilerplate from
* the fenced code. Existing snippets that include the PHP opening tag or the
* complete preamble are normalized without duplicating either line.
*
* @param string $code Snippet code extracted from a DocBlock fence.
*
* @return string Complete runnable PHP code.
*/
function add_docblock_php_snippet_preamble( $code ) {
$preamble = "<?php\nrequire '/wordpress/wp-load.php';";

if ( 0 === strpos( $code, $preamble ) ) {
return $code;
}

if ( 0 === strpos( $code, '<?php' ) ) {
$code = substr( $code, strlen( '<?php' ) );
$code = preg_replace( '/^[ \t]*\n?/', '', $code, 1 );
}

return $preamble . ( '' === $code ? '' : "\n" . $code );
}

/**
* Checks whether two fences are separated only by blank DocBlock lines.
*
Expand Down
12 changes: 0 additions & 12 deletions tests/phpunit/tests/export/docblocks.inc
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ class Test_Class {
* This is a docblock for a class property.
*
* ```php interactive setup-blueprint=file-greeting
* <?php
* // This property snippet line ends in a period.
* @unlink( '/tmp/phpdoc-parser-property' );
* require '/wordpress/wp-load.php';
* echo docs_file_greeting();
* ```
*
Expand Down Expand Up @@ -104,8 +102,6 @@ class Test_Class {
* ```
*
* ```php interactive
* <?php
* require '/wordpress/wp-load.php';
* echo docs_fixture_greeting();
* ```
*
Expand Down Expand Up @@ -134,8 +130,6 @@ class Test_Class {
* ```
*
* ```php interactive setup-blueprint=shared-greeting
* <?php
* require '/wordpress/wp-load.php';
* echo docs_shared_greeting( 'first' );
* ```
*
Expand All @@ -144,8 +138,6 @@ class Test_Class {
* ```
*
* ```php interactive setup-blueprint=shared-greeting
* <?php
* require '/wordpress/wp-load.php';
* echo docs_shared_greeting( 'second' );
* ```
*
Expand All @@ -162,8 +154,6 @@ class Test_Class {
* This is a method docblock with a file-level setup Blueprint.
*
* ```php interactive setup-blueprint=file-greeting
* <?php
* require '/wordpress/wp-load.php';
* echo docs_file_greeting();
* ```
*
Expand Down Expand Up @@ -191,8 +181,6 @@ $var = apply_filters_ref_array( 'test_ref_array_filter', array( &$var ) );
* A test action.
*
* ```php interactive setup-blueprint=file-greeting
* <?php
* require '/wordpress/wp-load.php';
* echo docs_file_greeting();
* ```
*
Expand Down
67 changes: 45 additions & 22 deletions tests/phpunit/tests/export/docblocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public function test_fence_first_docblocks() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\n" .
'code' => "<?php\nrequire '/wordpress/wp-load.php';\n" .
"@unlink( '/tmp/phpdoc-parser-file-review' );\n" .
"@! file_exists( '/tmp/phpdoc-parser-file-review' );\n" .
"echo 'file fence';",
Expand All @@ -134,15 +134,15 @@ public function test_fence_first_docblocks() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\n\necho 'fence first';",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\n\necho 'fence first';",
'blueprint' => 'shared',
),
),
$file['functions'][0]['doc']['code_snippets']
);

$this->assertSame(
"<?php\n// This source line ends in a period.\necho 'period split';",
"<?php\nrequire '/wordpress/wp-load.php';\n// This source line ends in a period.\necho 'period split';",
$file['functions'][1]['doc']['code_snippets'][0]['code']
);
$this->assertSame(
Expand All @@ -154,7 +154,7 @@ public function test_fence_first_docblocks() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\n" .
'code' => "<?php\nrequire '/wordpress/wp-load.php';\n" .
"@_before( 'not parsed before a letter-named tag' );\n" .
"@unlink( '/tmp/phpdoc-parser-review' );\n" .
"@! file_exists( '/tmp/phpdoc-parser-review' );\n" .
Expand Down Expand Up @@ -362,22 +362,22 @@ public function code_snippet_fence_delimiters() {
return array(
'smaller runs stay inside a larger fence' => array(
"````php interactive\n<?php\n```\necho 'inside';\n```\n````\n````expected-output\nouter\n````",
"<?php\n```\necho 'inside';\n```",
"<?php\nrequire '/wordpress/wp-load.php';\n```\necho 'inside';\n```",
'outer',
),
'different runs and text do not close a fence' => array(
"```php interactive\n<?php\n````\necho 'inside';\n``` not a closer\necho 'still inside';\n```\n```expected-output\nexact\n```",
"<?php\n````\necho 'inside';\n``` not a closer\necho 'still inside';",
"<?php\nrequire '/wordpress/wp-load.php';\n````\necho 'inside';\n``` not a closer\necho 'still inside';",
'exact',
),
'arbitrary indentation is removed from content' => array(
" ```php interactive\n <?php\n echo 'indented';\n\t```\n ```expected-output\n indented\n```",
"<?php\n echo 'indented';",
"<?php\nrequire '/wordpress/wp-load.php';\n echo 'indented';",
'indented',
),
'three leading spaces are accepted' => array(
" ```php interactive\n<?php echo 'three';\n```\n```expected-output\nthree\n ```",
"<?php echo 'three';",
"<?php\nrequire '/wordpress/wp-load.php';\necho 'three';",
'three',
),
);
Expand Down Expand Up @@ -489,15 +489,14 @@ public function test_code_snippet_without_metadata() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho 'No metadata';",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho 'No metadata';",
),
),
\WP_Parser\export_docblock_code_snippets(
implode(
"\n",
array(
'```php interactive',
'<?php',
'echo \'No metadata\';',
'```',
'',
Expand All @@ -510,6 +509,33 @@ public function test_code_snippet_without_metadata() {
);
}

/**
* Test that interactive snippets receive a complete WordPress PHP preamble.
*/
public function test_code_snippet_includes_wordpress_preamble() {

$preamble = "<?php\nrequire '/wordpress/wp-load.php';";

$this->assertSame(
$preamble . "\necho 'body only';",
\WP_Parser\export_docblock_code_snippets(
"```php interactive\necho 'body only';\n```"
)[0]['code']
);
$this->assertSame(
$preamble . "\necho 'opening tag';",
\WP_Parser\export_docblock_code_snippets(
"```php interactive\n<?php\necho 'opening tag';\n```"
)[0]['code']
);
$this->assertSame(
$preamble . "\necho 'complete preamble';",
\WP_Parser\export_docblock_code_snippets(
"```php interactive\n" . $preamble . "\necho 'complete preamble';\n```"
)[0]['code']
);
}

/**
* Test that unsupported info strings remain ordinary documentation.
*
Expand Down Expand Up @@ -608,11 +634,11 @@ public function test_code_snippet_inline_placeholders() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho step_one();",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho step_one();",
),
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho step_two();",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho step_two();",
'expected_output' => 'done',
),
),
Expand Down Expand Up @@ -779,7 +805,7 @@ public function test_code_snippet_metadata_rejects_extra_arguments() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho docs_case_fixture();",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho docs_case_fixture();",
),
),
\WP_Parser\export_docblock_code_snippets(
Expand All @@ -790,7 +816,6 @@ public function test_code_snippet_metadata_rejects_extra_arguments() {
'{"steps":[]}',
'```',
'```php interactive',
'<?php',
'echo docs_case_fixture();',
'```',
'```expected-output copied from a run',
Expand Down Expand Up @@ -1066,12 +1091,12 @@ public function test_code_snippet_metadata_boundaries() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho 'First';",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho 'First';",
'expected_output' => 'First',
),
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho 'Second';",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho 'Second';",
'blueprint' => array(
'steps' => array(
array(
Expand All @@ -1088,7 +1113,6 @@ public function test_code_snippet_metadata_boundaries() {
"\n",
array(
'```php interactive',
'<?php',
'echo \'First\';',
'```',
'```expected-output',
Expand All @@ -1098,7 +1122,6 @@ public function test_code_snippet_metadata_boundaries() {
'{"steps":[{"step":"writeFile","path":"/tmp/second.php","data":"<?php echo \"second setup\";"}]}',
'```',
'```php interactive',
'<?php',
'echo \'Second\';',
'```',
)
Expand Down Expand Up @@ -1188,18 +1211,18 @@ public function test_code_snippet_named_setup_blueprints() {
array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho \"first\";",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho \"first\";",
'expected_output' => 'first',
'blueprint' => 'shared',
),
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho \"no leaked inline blueprint\";",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho \"no leaked inline blueprint\";",
'expected_output' => 'no leaked inline blueprint',
),
array(
'type' => 'php-code-snippet',
'code' => "<?php\necho \"third\";",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\necho \"third\";",
'blueprint' => 'shared',
),
),
Expand All @@ -1221,7 +1244,7 @@ public function test_property_docblocks() {
'code_snippets' => array(
array(
'type' => 'php-code-snippet',
'code' => "<?php\n// This property snippet line ends in a period.\n@unlink( '/tmp/phpdoc-parser-property' );\nrequire '/wordpress/wp-load.php';\necho docs_file_greeting();",
'code' => "<?php\nrequire '/wordpress/wp-load.php';\n// This property snippet line ends in a period.\n@unlink( '/tmp/phpdoc-parser-property' );\necho docs_file_greeting();",
'expected_output' => 'Hello from the file setup',
'blueprint' => 'file-greeting',
),
Expand Down
4 changes: 0 additions & 4 deletions tests/phpunit/tests/export/fence-first-docblocks.inc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* ```
*
* ```php interactive setup-blueprint=shared
* <?php
* @unlink( '/tmp/phpdoc-parser-file-review' );
* @! file_exists( '/tmp/phpdoc-parser-file-review' );
* echo 'file fence';
Expand All @@ -15,7 +14,6 @@

/**
* ```php interactive setup-blueprint=shared
* <?php
*
* echo 'fence first';
* ```
Expand All @@ -25,7 +23,6 @@ function fence_first_docblock_example() {

/**
* ```php interactive
* <?php
* // This source line ends in a period.
* echo 'period split';
* ```
Expand All @@ -37,7 +34,6 @@ function fence_first_period_example() {
* A snippet containing PHP's error-suppression operator.
*
* ```php interactive
* <?php
* @_before( 'not parsed before a letter-named tag' );
* @unlink( '/tmp/phpdoc-parser-review' );
* @! file_exists( '/tmp/phpdoc-parser-review' );
Expand Down