From 280a3ce56d9eebbe8728d9c4f3c63f8fb11c2d75 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Sat, 30 Aug 2014 11:08:15 +0200 Subject: [PATCH 1/2] Add support for DocBlock template markers DocBlocks may start with #@+ and #@- to indicate that they are (the start) of a DocBlock template or the end of a template. In this commit I have changed the way a DocBlock is parsed to interpret this information and added tests to show for it. In addition I have added more comments to the Regular Expression responsible for splitting a DocBlock to show the business rules more clearly. This is the first step in implementing https://github.com/phpDocumentor/phpDocumentor2/issues/42. --- src/phpDocumentor/Reflection/DocBlock.php | 152 +++++++++++------- .../phpDocumentor/Reflection/DocBlockTest.php | 30 ++++ 2 files changed, 124 insertions(+), 58 deletions(-) diff --git a/src/phpDocumentor/Reflection/DocBlock.php b/src/phpDocumentor/Reflection/DocBlock.php index 7058562..5d76ba3 100644 --- a/src/phpDocumentor/Reflection/DocBlock.php +++ b/src/phpDocumentor/Reflection/DocBlock.php @@ -46,6 +46,12 @@ class DocBlock implements \Reflector /** @var Location Information about the location of this DocBlock. */ protected $location = null; + /** @var bool Is this DocBlock (the start of) a template? */ + protected $isTemplateStart = false; + + /** @var bool Does this DocBlock signify the end of a DocBlock template? */ + protected $isTemplateEnd = false; + /** * Parses the given docblock and populates the member fields. * @@ -81,7 +87,9 @@ class DocBlock implements \Reflector $docblock = $this->cleanInput($docblock); - list($short, $long, $tags) = $this->splitDocBlock($docblock); + list($templateMarker, $short, $long, $tags) = $this->splitDocBlock($docblock); + $this->isTemplateStart = $templateMarker === '#@+'; + $this->isTemplateEnd = $templateMarker === '#@-'; $this->short_description = $short; $this->long_description = new DocBlock\Description($long, $this); $this->parseTags($tags); @@ -119,74 +127,86 @@ class DocBlock implements \Reflector } /** - * Splits the DocBlock into a short description, long description and - * block of tags. + * Splits the DocBlock into a template marker, summary, description and block of tags. * * @param string $comment Comment to split into the sub-parts. * - * @author RichardJ Special thanks to RichardJ for the regex responsible - * for the split. + * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. + * @author Mike van Riel for extending the regex with template marker support. * - * @return string[] containing the short-, long description and an element - * containing the tags. + * @return string[] containing the template marker (if any), summary, description and a string containing the tags. */ protected function splitDocBlock($comment) { + // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This + // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the + // performance impact of running a regular expression if (strpos($comment, '@') === 0) { - $matches = array('', '', $comment); - } else { - // clears all extra horizontal whitespace from the line endings - // to prevent parsing issues - $comment = preg_replace('/\h*$/Sum', '', $comment); - - /* - * Splits the docblock into a short description, long description and - * tags section - * - The short description is started from the first character until - * a dot is encountered followed by a newline OR - * two consecutive newlines (horizontal whitespace is taken into - * account to consider spacing errors) - * - The long description, any character until a new line is - * encountered followed by an @ and word characters (a tag). - * This is optional. - * - Tags; the remaining characters - * - * Big thanks to RichardJ for contributing this Regular Expression - */ - preg_match( - '/ - \A ( - [^\n.]+ - (?: - (?! \. \n | \n{2} ) # disallow the first seperator here - [\n.] (?! [ \t]* @\pL ) # disallow second seperator - [^\n.]+ - )* - \.? - ) - (?: - \s* # first seperator (actually newlines but it\'s all whitespace) - (?! @\pL ) # disallow the rest, to make sure this one doesn\'t match, - #if it doesn\'t exist - ( - [^\n]+ - (?: \n+ - (?! [ \t]* @\pL ) # disallow second seperator (@param) - [^\n]+ - )* - ) - )? - (\s+ [\s\S]*)? # everything that follows - /ux', - $comment, - $matches - ); - array_shift($matches); + return array('', '', '', $comment); } - while (count($matches) < 3) { + // clears all extra horizontal whitespace from the line endings to prevent parsing issues + $comment = preg_replace('/\h*$/Sum', '', $comment); + + /* + * Splits the docblock into a template marker, short description, long description and tags section + * + * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may + * occur after it and will be stripped). + * - The short description is started from the first character until a dot is encountered followed by a + * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing + * errors). This is optional. + * - The long description, any character until a new line is encountered followed by an @ and word + * characters (a tag). This is optional. + * - Tags; the remaining characters + * + * Big thanks to RichardJ for contributing this Regular Expression + */ + preg_match( + '/ + \A + # 1. Extract the template marker + (?:(\#\@\+|\#\@\-)\n?)? + + # 2. Extract the summary + (?: + (?! @\pL ) # The summary may not start with an @ + ( + [^\n.]+ + (?: + (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines + [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line + [^\n.]+ # Include anything else + )* + \.? + )? + ) + + # 3. Extract the description + (?: + \s* # Some form of whitespace _must_ precede a description because a summary must be there + (?! @\pL ) # The description may not start with an @ + ( + [^\n]+ + (?: \n+ + (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line + [^\n]+ # Include anything else + )* + ) + )? + + # 4. Extract the tags (anything that follows) + (\s+ [\s\S]*)? # everything that follows + /ux', + $comment, + $matches + ); + array_shift($matches); + + while (count($matches) < 4) { $matches[] = ''; } + return $matches; } @@ -257,7 +277,7 @@ class DocBlock implements \Reflector */ public function setText($comment) { - list($short, $long) = $this->splitDocBlock($comment); + list(,$short, $long) = $this->splitDocBlock($comment); $this->short_description = $short; $this->long_description = new DocBlock\Description($long, $this); return $this; @@ -282,6 +302,22 @@ class DocBlock implements \Reflector return $this->long_description; } + /** + * @return boolean + */ + public function isTemplateStart() + { + return $this->isTemplateStart; + } + + /** + * @return boolean + */ + public function isTemplateEnd() + { + return $this->isTemplateEnd; + } + /** * Returns the current context. * diff --git a/tests/phpDocumentor/Reflection/DocBlockTest.php b/tests/phpDocumentor/Reflection/DocBlockTest.php index 488db3e..fcce36e 100644 --- a/tests/phpDocumentor/Reflection/DocBlockTest.php +++ b/tests/phpDocumentor/Reflection/DocBlockTest.php @@ -71,6 +71,7 @@ DOCBLOCK; /** * @covers \phpDocumentor\Reflection\DocBlock::splitDocBlock + * @group test * * @return void */ @@ -91,6 +92,35 @@ DOCBLOCK; $this->assertFalse($object->hasTag('category')); } + public function testIfStartOfTemplateIsDiscovered() + { + $fixture = <<assertEquals('', $object->getShortDescription()); + $this->assertEquals('', $object->getLongDescription()->getContents()); + $this->assertCount(2, $object->getTags()); + $this->assertTrue($object->hasTag('see')); + $this->assertTrue($object->hasTag('return')); + $this->assertFalse($object->hasTag('category')); + $this->assertTrue($object->isTemplateStart()); + } + + public function testIfEndOfTemplateIsDiscovered() + { + $fixture = <<assertEquals('', $object->getShortDescription()); + $this->assertEquals('', $object->getLongDescription()->getContents()); + $this->assertTrue($object->isTemplateEnd()); + } + /** * @covers \phpDocumentor\Reflection\DocBlock::cleanInput * From 21feb61eb5f6d30428715e9aedd945ca5746cc54 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Sat, 30 Aug 2014 11:16:26 +0200 Subject: [PATCH 2/2] Add missing DocBlock (info). --- src/phpDocumentor/Reflection/DocBlock.php | 22 +++++++++++++++++++ .../phpDocumentor/Reflection/DocBlockTest.php | 9 ++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/phpDocumentor/Reflection/DocBlock.php b/src/phpDocumentor/Reflection/DocBlock.php index 5d76ba3..02968b1 100644 --- a/src/phpDocumentor/Reflection/DocBlock.php +++ b/src/phpDocumentor/Reflection/DocBlock.php @@ -303,6 +303,24 @@ class DocBlock implements \Reflector } /** + * Returns whether this DocBlock is the start of a Template section. + * + * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker + * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. + * + * An example of such an opening is: + * + * ``` + * /**#@+ + * * My DocBlock + * * / + * ``` + * + * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all + * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). + * + * @see self::isTemplateEnd() for the check whether a closing marker was provided. + * * @return boolean */ public function isTemplateStart() @@ -311,6 +329,10 @@ class DocBlock implements \Reflector } /** + * Returns whether this DocBlock is the end of a Template section. + * + * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. + * * @return boolean */ public function isTemplateEnd() diff --git a/tests/phpDocumentor/Reflection/DocBlockTest.php b/tests/phpDocumentor/Reflection/DocBlockTest.php index fcce36e..30eedfc 100644 --- a/tests/phpDocumentor/Reflection/DocBlockTest.php +++ b/tests/phpDocumentor/Reflection/DocBlockTest.php @@ -71,8 +71,7 @@ DOCBLOCK; /** * @covers \phpDocumentor\Reflection\DocBlock::splitDocBlock - * @group test - * + * * @return void */ public function testConstructWithTagsOnly() @@ -92,6 +91,9 @@ DOCBLOCK; $this->assertFalse($object->hasTag('category')); } + /** + * @covers \phpDocumentor\Reflection\DocBlock::isTemplateStart + */ public function testIfStartOfTemplateIsDiscovered() { $fixture = <<assertTrue($object->isTemplateStart()); } + /** + * @covers \phpDocumentor\Reflection\DocBlock::isTemplateEnd + */ public function testIfEndOfTemplateIsDiscovered() { $fixture = <<