Skip to content

Commit 280a3ce

Browse files
committed
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 phpDocumentor/phpDocumentor#42.
1 parent 0604d62 commit 280a3ce

File tree

2 files changed

+124
-58
lines changed

2 files changed

+124
-58
lines changed

src/phpDocumentor/Reflection/DocBlock.php

Lines changed: 94 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 <me@mikevanriel.com> 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,22 @@ public function getLongDescription()
282302
return $this->long_description;
283303
}
284304

305+
/**
306+
* @return boolean
307+
*/
308+
public function isTemplateStart()
309+
{
310+
return $this->isTemplateStart;
311+
}
312+
313+
/**
314+
* @return boolean
315+
*/
316+
public function isTemplateEnd()
317+
{
318+
return $this->isTemplateEnd;
319+
}
320+
285321
/**
286322
* Returns the current context.
287323
*

tests/phpDocumentor/Reflection/DocBlockTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public function testConstruct()
7171

7272
/**
7373
* @covers \phpDocumentor\Reflection\DocBlock::splitDocBlock
74+
* @group test
7475
*
7576
* @return void
7677
*/
@@ -91,6 +92,35 @@ public function testConstructWithTagsOnly()
9192
$this->assertFalse($object->hasTag('category'));
9293
}
9394

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

0 commit comments

Comments
 (0)