Skip to content

Commit fd0ac20

Browse files
committed
Merge pull request #39 from mvriel/feature/phpDocumentor2/42
Add support for DocBlock template markers
2 parents 0604d62 + 21feb61 commit fd0ac20

File tree

2 files changed

+152
-59
lines changed

2 files changed

+152
-59
lines changed

src/phpDocumentor/Reflection/DocBlock.php

Lines changed: 116 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ class DocBlock implements \Reflector
4646
/** @var Location Information about the location of this DocBlock. */
4747
protected $location = null;
4848

49+
/** @var bool Is this DocBlock (the start of) a template? */
50+
protected $isTemplateStart = false;
51+
52+
/** @var bool Does this DocBlock signify the end of a DocBlock template? */
53+
protected $isTemplateEnd = false;
54+
4955
/**
5056
* Parses the given docblock and populates the member fields.
5157
*
@@ -81,7 +87,9 @@ public function __construct(
8187

8288
$docblock = $this->cleanInput($docblock);
8389

84-
list($short, $long, $tags) = $this->splitDocBlock($docblock);
90+
list($templateMarker, $short, $long, $tags) = $this->splitDocBlock($docblock);
91+
$this->isTemplateStart = $templateMarker === '#@+';
92+
$this->isTemplateEnd = $templateMarker === '#@-';
8593
$this->short_description = $short;
8694
$this->long_description = new DocBlock\Description($long, $this);
8795
$this->parseTags($tags);
@@ -119,74 +127,86 @@ protected function cleanInput($comment)
119127
}
120128

121129
/**
122-
* Splits the DocBlock into a short description, long description and
123-
* block of tags.
130+
* Splits the DocBlock into a template marker, summary, description and block of tags.
124131
*
125132
* @param string $comment Comment to split into the sub-parts.
126133
*
127-
* @author RichardJ Special thanks to RichardJ for the regex responsible
128-
* for the split.
134+
* @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
135+
* @author Mike van Riel <[email protected]> for extending the regex with template marker support.
129136
*
130-
* @return string[] containing the short-, long description and an element
131-
* containing the tags.
137+
* @return string[] containing the template marker (if any), summary, description and a string containing the tags.
132138
*/
133139
protected function splitDocBlock($comment)
134140
{
141+
// Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
142+
// method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
143+
// performance impact of running a regular expression
135144
if (strpos($comment, '@') === 0) {
136-
$matches = array('', '', $comment);
137-
} else {
138-
// clears all extra horizontal whitespace from the line endings
139-
// to prevent parsing issues
140-
$comment = preg_replace('/\h*$/Sum', '', $comment);
141-
142-
/*
143-
* Splits the docblock into a short description, long description and
144-
* tags section
145-
* - The short description is started from the first character until
146-
* a dot is encountered followed by a newline OR
147-
* two consecutive newlines (horizontal whitespace is taken into
148-
* account to consider spacing errors)
149-
* - The long description, any character until a new line is
150-
* encountered followed by an @ and word characters (a tag).
151-
* This is optional.
152-
* - Tags; the remaining characters
153-
*
154-
* Big thanks to RichardJ for contributing this Regular Expression
155-
*/
156-
preg_match(
157-
'/
158-
\A (
159-
[^\n.]+
160-
(?:
161-
(?! \. \n | \n{2} ) # disallow the first seperator here
162-
[\n.] (?! [ \t]* @\pL ) # disallow second seperator
163-
[^\n.]+
164-
)*
165-
\.?
166-
)
167-
(?:
168-
\s* # first seperator (actually newlines but it\'s all whitespace)
169-
(?! @\pL ) # disallow the rest, to make sure this one doesn\'t match,
170-
#if it doesn\'t exist
171-
(
172-
[^\n]+
173-
(?: \n+
174-
(?! [ \t]* @\pL ) # disallow second seperator (@param)
175-
[^\n]+
176-
)*
177-
)
178-
)?
179-
(\s+ [\s\S]*)? # everything that follows
180-
/ux',
181-
$comment,
182-
$matches
183-
);
184-
array_shift($matches);
145+
return array('', '', '', $comment);
185146
}
186147

187-
while (count($matches) < 3) {
148+
// clears all extra horizontal whitespace from the line endings to prevent parsing issues
149+
$comment = preg_replace('/\h*$/Sum', '', $comment);
150+
151+
/*
152+
* Splits the docblock into a template marker, short description, long description and tags section
153+
*
154+
* - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
155+
* occur after it and will be stripped).
156+
* - The short description is started from the first character until a dot is encountered followed by a
157+
* newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
158+
* errors). This is optional.
159+
* - The long description, any character until a new line is encountered followed by an @ and word
160+
* characters (a tag). This is optional.
161+
* - Tags; the remaining characters
162+
*
163+
* Big thanks to RichardJ for contributing this Regular Expression
164+
*/
165+
preg_match(
166+
'/
167+
\A
168+
# 1. Extract the template marker
169+
(?:(\#\@\+|\#\@\-)\n?)?
170+
171+
# 2. Extract the summary
172+
(?:
173+
(?! @\pL ) # The summary may not start with an @
174+
(
175+
[^\n.]+
176+
(?:
177+
(?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines
178+
[\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
179+
[^\n.]+ # Include anything else
180+
)*
181+
\.?
182+
)?
183+
)
184+
185+
# 3. Extract the description
186+
(?:
187+
\s* # Some form of whitespace _must_ precede a description because a summary must be there
188+
(?! @\pL ) # The description may not start with an @
189+
(
190+
[^\n]+
191+
(?: \n+
192+
(?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
193+
[^\n]+ # Include anything else
194+
)*
195+
)
196+
)?
197+
198+
# 4. Extract the tags (anything that follows)
199+
(\s+ [\s\S]*)? # everything that follows
200+
/ux',
201+
$comment,
202+
$matches
203+
);
204+
array_shift($matches);
205+
206+
while (count($matches) < 4) {
188207
$matches[] = '';
189208
}
209+
190210
return $matches;
191211
}
192212

@@ -257,7 +277,7 @@ public function getText()
257277
*/
258278
public function setText($comment)
259279
{
260-
list($short, $long) = $this->splitDocBlock($comment);
280+
list(,$short, $long) = $this->splitDocBlock($comment);
261281
$this->short_description = $short;
262282
$this->long_description = new DocBlock\Description($long, $this);
263283
return $this;
@@ -282,6 +302,44 @@ public function getLongDescription()
282302
return $this->long_description;
283303
}
284304

305+
/**
306+
* Returns whether this DocBlock is the start of a Template section.
307+
*
308+
* A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker
309+
* (`#@+`) that is appended directly after the opening `/**` of a DocBlock.
310+
*
311+
* An example of such an opening is:
312+
*
313+
* ```
314+
* /**#@+
315+
* * My DocBlock
316+
* * /
317+
* ```
318+
*
319+
* The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all
320+
* elements that follow until another DocBlock is found that contains the closing marker (`#@-`).
321+
*
322+
* @see self::isTemplateEnd() for the check whether a closing marker was provided.
323+
*
324+
* @return boolean
325+
*/
326+
public function isTemplateStart()
327+
{
328+
return $this->isTemplateStart;
329+
}
330+
331+
/**
332+
* Returns whether this DocBlock is the end of a Template section.
333+
*
334+
* @see self::isTemplateStart() for a more complete description of the Docblock Template functionality.
335+
*
336+
* @return boolean
337+
*/
338+
public function isTemplateEnd()
339+
{
340+
return $this->isTemplateEnd;
341+
}
342+
285343
/**
286344
* Returns the current context.
287345
*

tests/phpDocumentor/Reflection/DocBlockTest.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function testConstruct()
7171

7272
/**
7373
* @covers \phpDocumentor\Reflection\DocBlock::splitDocBlock
74-
*
74+
*
7575
* @return void
7676
*/
7777
public function testConstructWithTagsOnly()
@@ -91,6 +91,41 @@ public function testConstructWithTagsOnly()
9191
$this->assertFalse($object->hasTag('category'));
9292
}
9393

94+
/**
95+
* @covers \phpDocumentor\Reflection\DocBlock::isTemplateStart
96+
*/
97+
public function testIfStartOfTemplateIsDiscovered()
98+
{
99+
$fixture = <<<DOCBLOCK
100+
/**#@+
101+
* @see \MyClass
102+
* @return void
103+
*/
104+
DOCBLOCK;
105+
$object = new DocBlock($fixture);
106+
$this->assertEquals('', $object->getShortDescription());
107+
$this->assertEquals('', $object->getLongDescription()->getContents());
108+
$this->assertCount(2, $object->getTags());
109+
$this->assertTrue($object->hasTag('see'));
110+
$this->assertTrue($object->hasTag('return'));
111+
$this->assertFalse($object->hasTag('category'));
112+
$this->assertTrue($object->isTemplateStart());
113+
}
114+
115+
/**
116+
* @covers \phpDocumentor\Reflection\DocBlock::isTemplateEnd
117+
*/
118+
public function testIfEndOfTemplateIsDiscovered()
119+
{
120+
$fixture = <<<DOCBLOCK
121+
/**#@-*/
122+
DOCBLOCK;
123+
$object = new DocBlock($fixture);
124+
$this->assertEquals('', $object->getShortDescription());
125+
$this->assertEquals('', $object->getLongDescription()->getContents());
126+
$this->assertTrue($object->isTemplateEnd());
127+
}
128+
94129
/**
95130
* @covers \phpDocumentor\Reflection\DocBlock::cleanInput
96131
*

0 commit comments

Comments
 (0)