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.
This commit is contained in:
Mike van Riel
2014-08-30 11:08:15 +02:00
parent 0604d62704
commit 280a3ce56d
2 changed files with 124 additions and 58 deletions
+68 -32
View File
@@ -46,6 +46,12 @@ class DocBlock implements \Reflector
/** @var Location Information about the location of this DocBlock. */ /** @var Location Information about the location of this DocBlock. */
protected $location = null; 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. * Parses the given docblock and populates the member fields.
* *
@@ -81,7 +87,9 @@ class DocBlock implements \Reflector
$docblock = $this->cleanInput($docblock); $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->short_description = $short;
$this->long_description = new DocBlock\Description($long, $this); $this->long_description = new DocBlock\Description($long, $this);
$this->parseTags($tags); $this->parseTags($tags);
@@ -119,74 +127,86 @@ class DocBlock implements \Reflector
} }
/** /**
* Splits the DocBlock into a short description, long description and * Splits the DocBlock into a template marker, summary, description and block of tags.
* block of tags.
* *
* @param string $comment Comment to split into the sub-parts. * @param string $comment Comment to split into the sub-parts.
* *
* @author RichardJ Special thanks to RichardJ for the regex responsible * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
* for the split. * @author Mike van Riel <[email protected]> for extending the regex with template marker support.
* *
* @return string[] containing the short-, long description and an element * @return string[] containing the template marker (if any), summary, description and a string containing the tags.
* containing the tags.
*/ */
protected function splitDocBlock($comment) 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) { if (strpos($comment, '@') === 0) {
$matches = array('', '', $comment); return array('', '', '', $comment);
} else { }
// clears all extra horizontal whitespace from the line endings
// to prevent parsing issues // clears all extra horizontal whitespace from the line endings to prevent parsing issues
$comment = preg_replace('/\h*$/Sum', '', $comment); $comment = preg_replace('/\h*$/Sum', '', $comment);
/* /*
* Splits the docblock into a short description, long description and * Splits the docblock into a template marker, short description, long description and tags section
* tags section *
* - The short description is started from the first character until * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
* a dot is encountered followed by a newline OR * occur after it and will be stripped).
* two consecutive newlines (horizontal whitespace is taken into * - The short description is started from the first character until a dot is encountered followed by a
* account to consider spacing errors) * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
* - The long description, any character until a new line is * errors). This is optional.
* encountered followed by an @ and word characters (a tag). * - The long description, any character until a new line is encountered followed by an @ and word
* This is optional. * characters (a tag). This is optional.
* - Tags; the remaining characters * - Tags; the remaining characters
* *
* Big thanks to RichardJ for contributing this Regular Expression * Big thanks to RichardJ for contributing this Regular Expression
*/ */
preg_match( preg_match(
'/ '/
\A ( \A
# 1. Extract the template marker
(?:(\#\@\+|\#\@\-)\n?)?
# 2. Extract the summary
(?:
(?! @\pL ) # The summary may not start with an @
(
[^\n.]+ [^\n.]+
(?: (?:
(?! \. \n | \n{2} ) # disallow the first seperator here (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines
[\n.] (?! [ \t]* @\pL ) # disallow second seperator [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
[^\n.]+ [^\n.]+ # Include anything else
)* )*
\.? \.?
)?
) )
# 3. Extract the description
(?: (?:
\s* # first seperator (actually newlines but it\'s all whitespace) \s* # Some form of whitespace _must_ precede a description because a summary must be there
(?! @\pL ) # disallow the rest, to make sure this one doesn\'t match, (?! @\pL ) # The description may not start with an @
#if it doesn\'t exist
( (
[^\n]+ [^\n]+
(?: \n+ (?: \n+
(?! [ \t]* @\pL ) # disallow second seperator (@param) (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
[^\n]+ [^\n]+ # Include anything else
)* )*
) )
)? )?
# 4. Extract the tags (anything that follows)
(\s+ [\s\S]*)? # everything that follows (\s+ [\s\S]*)? # everything that follows
/ux', /ux',
$comment, $comment,
$matches $matches
); );
array_shift($matches); array_shift($matches);
}
while (count($matches) < 3) { while (count($matches) < 4) {
$matches[] = ''; $matches[] = '';
} }
return $matches; return $matches;
} }
@@ -257,7 +277,7 @@ class DocBlock implements \Reflector
*/ */
public function setText($comment) public function setText($comment)
{ {
list($short, $long) = $this->splitDocBlock($comment); list(,$short, $long) = $this->splitDocBlock($comment);
$this->short_description = $short; $this->short_description = $short;
$this->long_description = new DocBlock\Description($long, $this); $this->long_description = new DocBlock\Description($long, $this);
return $this; return $this;
@@ -282,6 +302,22 @@ class DocBlock implements \Reflector
return $this->long_description; return $this->long_description;
} }
/**
* @return boolean
*/
public function isTemplateStart()
{
return $this->isTemplateStart;
}
/**
* @return boolean
*/
public function isTemplateEnd()
{
return $this->isTemplateEnd;
}
/** /**
* Returns the current context. * Returns the current context.
* *
@@ -71,6 +71,7 @@ DOCBLOCK;
/** /**
* @covers \phpDocumentor\Reflection\DocBlock::splitDocBlock * @covers \phpDocumentor\Reflection\DocBlock::splitDocBlock
* @group test
* *
* @return void * @return void
*/ */
@@ -91,6 +92,35 @@ DOCBLOCK;
$this->assertFalse($object->hasTag('category')); $this->assertFalse($object->hasTag('category'));
} }
public function testIfStartOfTemplateIsDiscovered()
{
$fixture = <<<DOCBLOCK
/**#@+
* @see \MyClass
* @return void
*/
DOCBLOCK;
$object = new DocBlock($fixture);
$this->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 = <<<DOCBLOCK
/**#@-*/
DOCBLOCK;
$object = new DocBlock($fixture);
$this->assertEquals('', $object->getShortDescription());
$this->assertEquals('', $object->getLongDescription()->getContents());
$this->assertTrue($object->isTemplateEnd());
}
/** /**
* @covers \phpDocumentor\Reflection\DocBlock::cleanInput * @covers \phpDocumentor\Reflection\DocBlock::cleanInput
* *