Security update to Drupal 8.4.6 with PHP held back to 7.0.27 to match the stoneboat...
[yaffs-website] / vendor / phpdocumentor / reflection-docblock / src / phpDocumentor / Reflection / DocBlock.php
1 <?php
2 /**
3  * phpDocumentor
4  *
5  * PHP Version 5.3
6  *
7  * @author    Mike van Riel <mike.vanriel@naenius.com>
8  * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
9  * @license   http://www.opensource.org/licenses/mit-license.php MIT
10  * @link      http://phpdoc.org
11  */
12
13 namespace phpDocumentor\Reflection;
14
15 use phpDocumentor\Reflection\DocBlock\Tag;
16 use phpDocumentor\Reflection\DocBlock\Context;
17 use phpDocumentor\Reflection\DocBlock\Location;
18
19 /**
20  * Parses the DocBlock for any structure.
21  *
22  * @author  Mike van Riel <mike.vanriel@naenius.com>
23  * @license http://www.opensource.org/licenses/mit-license.php MIT
24  * @link    http://phpdoc.org
25  */
26 class DocBlock implements \Reflector
27 {
28     /** @var string The opening line for this docblock. */
29     protected $short_description = '';
30
31     /**
32      * @var DocBlock\Description The actual
33      *     description for this docblock.
34      */
35     protected $long_description = null;
36
37     /**
38      * @var Tag[] An array containing all
39      *     the tags in this docblock; except inline.
40      */
41     protected $tags = array();
42
43     /** @var Context Information about the context of this DocBlock. */
44     protected $context = null;
45
46     /** @var Location Information about the location of this DocBlock. */
47     protected $location = null;
48
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
55     /**
56      * Parses the given docblock and populates the member fields.
57      *
58      * The constructor may also receive namespace information such as the
59      * current namespace and aliases. This information is used by some tags
60      * (e.g. @return, @param, etc.) to turn a relative Type into a FQCN.
61      *
62      * @param \Reflector|string $docblock A docblock comment (including
63      *     asterisks) or reflector supporting the getDocComment method.
64      * @param Context           $context  The context in which the DocBlock
65      *     occurs.
66      * @param Location          $location The location within the file that this
67      *     DocBlock occurs in.
68      *
69      * @throws \InvalidArgumentException if the given argument does not have the
70      *     getDocComment method.
71      */
72     public function __construct(
73         $docblock,
74         Context $context = null,
75         Location $location = null
76     ) {
77         if (is_object($docblock)) {
78             if (!method_exists($docblock, 'getDocComment')) {
79                 throw new \InvalidArgumentException(
80                     'Invalid object passed; the given reflector must support '
81                     . 'the getDocComment method'
82                 );
83             }
84
85             $docblock = $docblock->getDocComment();
86         }
87
88         $docblock = $this->cleanInput($docblock);
89
90         list($templateMarker, $short, $long, $tags) = $this->splitDocBlock($docblock);
91         $this->isTemplateStart = $templateMarker === '#@+';
92         $this->isTemplateEnd = $templateMarker === '#@-';
93         $this->short_description = $short;
94         $this->long_description = new DocBlock\Description($long, $this);
95         $this->parseTags($tags);
96
97         $this->context  = $context;
98         $this->location = $location;
99     }
100
101     /**
102      * Strips the asterisks from the DocBlock comment.
103      *
104      * @param string $comment String containing the comment text.
105      *
106      * @return string
107      */
108     protected function cleanInput($comment)
109     {
110         $comment = trim(
111             preg_replace(
112                 '#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u',
113                 '$1',
114                 $comment
115             )
116         );
117
118         // reg ex above is not able to remove */ from a single line docblock
119         if (substr($comment, -2) == '*/') {
120             $comment = trim(substr($comment, 0, -2));
121         }
122
123         // normalize strings
124         $comment = str_replace(array("\r\n", "\r"), "\n", $comment);
125
126         return $comment;
127     }
128
129     /**
130      * Splits the DocBlock into a template marker, summary, description and block of tags.
131      *
132      * @param string $comment Comment to split into the sub-parts.
133      *
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.
136      *
137      * @return string[] containing the template marker (if any), summary, description and a string containing the tags.
138      */
139     protected function splitDocBlock($comment)
140     {
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
144         if (strpos($comment, '@') === 0) {
145             return array('', '', '', $comment);
146         }
147
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) {
207             $matches[] = '';
208         }
209
210         return $matches;
211     }
212
213     /**
214      * Creates the tag objects.
215      *
216      * @param string $tags Tag block to parse.
217      *
218      * @return void
219      */
220     protected function parseTags($tags)
221     {
222         $result = array();
223         $tags = trim($tags);
224         if ('' !== $tags) {
225             if ('@' !== $tags[0]) {
226                 throw new \LogicException(
227                     'A tag block started with text instead of an actual tag,'
228                     . ' this makes the tag block invalid: ' . $tags
229                 );
230             }
231             foreach (explode("\n", $tags) as $tag_line) {
232                 if (isset($tag_line[0]) && ($tag_line[0] === '@')) {
233                     $result[] = $tag_line;
234                 } else {
235                     $result[count($result) - 1] .= "\n" . $tag_line;
236                 }
237             }
238
239             // create proper Tag objects
240             foreach ($result as $key => $tag_line) {
241                 $result[$key] = Tag::createInstance(trim($tag_line), $this);
242             }
243         }
244
245         $this->tags = $result;
246     }
247
248     /**
249      * Gets the text portion of the doc block.
250      * 
251      * Gets the text portion (short and long description combined) of the doc
252      * block.
253      * 
254      * @return string The text portion of the doc block.
255      */
256     public function getText()
257     {
258         $short = $this->getShortDescription();
259         $long = $this->getLongDescription()->getContents();
260
261         if ($long) {
262             return "{$short}\n\n{$long}";
263         } else {
264             return $short;
265         }
266     }
267
268     /**
269      * Set the text portion of the doc block.
270      * 
271      * Sets the text portion (short and long description combined) of the doc
272      * block.
273      *
274      * @param string $docblock The new text portion of the doc block.
275      * 
276      * @return $this This doc block.
277      */
278     public function setText($comment)
279     {
280         list(,$short, $long) = $this->splitDocBlock($comment);
281         $this->short_description = $short;
282         $this->long_description = new DocBlock\Description($long, $this);
283         return $this;
284     }
285     /**
286      * Returns the opening line or also known as short description.
287      *
288      * @return string
289      */
290     public function getShortDescription()
291     {
292         return $this->short_description;
293     }
294
295     /**
296      * Returns the full description or also known as long description.
297      *
298      * @return DocBlock\Description
299      */
300     public function getLongDescription()
301     {
302         return $this->long_description;
303     }
304
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
343     /**
344      * Returns the current context.
345      *
346      * @return Context
347      */
348     public function getContext()
349     {
350         return $this->context;
351     }
352
353     /**
354      * Returns the current location.
355      *
356      * @return Location
357      */
358     public function getLocation()
359     {
360         return $this->location;
361     }
362
363     /**
364      * Returns the tags for this DocBlock.
365      *
366      * @return Tag[]
367      */
368     public function getTags()
369     {
370         return $this->tags;
371     }
372
373     /**
374      * Returns an array of tags matching the given name. If no tags are found
375      * an empty array is returned.
376      *
377      * @param string $name String to search by.
378      *
379      * @return Tag[]
380      */
381     public function getTagsByName($name)
382     {
383         $result = array();
384
385         /** @var Tag $tag */
386         foreach ($this->getTags() as $tag) {
387             if ($tag->getName() != $name) {
388                 continue;
389             }
390
391             $result[] = $tag;
392         }
393
394         return $result;
395     }
396
397     /**
398      * Checks if a tag of a certain type is present in this DocBlock.
399      *
400      * @param string $name Tag name to check for.
401      *
402      * @return bool
403      */
404     public function hasTag($name)
405     {
406         /** @var Tag $tag */
407         foreach ($this->getTags() as $tag) {
408             if ($tag->getName() == $name) {
409                 return true;
410             }
411         }
412
413         return false;
414     }
415
416     /**
417      * Appends a tag at the end of the list of tags.
418      *
419      * @param Tag $tag The tag to add.
420      *
421      * @return Tag The newly added tag.
422      *
423      * @throws \LogicException When the tag belongs to a different DocBlock.
424      */
425     public function appendTag(Tag $tag)
426     {
427         if (null === $tag->getDocBlock()) {
428             $tag->setDocBlock($this);
429         }
430
431         if ($tag->getDocBlock() === $this) {
432             $this->tags[] = $tag;
433         } else {
434             throw new \LogicException(
435                 'This tag belongs to a different DocBlock object.'
436             );
437         }
438
439         return $tag;
440     }
441
442
443     /**
444      * Builds a string representation of this object.
445      *
446      * @todo determine the exact format as used by PHP Reflection and
447      *     implement it.
448      *
449      * @return string
450      * @codeCoverageIgnore Not yet implemented
451      */
452     public static function export()
453     {
454         throw new \Exception('Not yet implemented');
455     }
456
457     /**
458      * Returns the exported information (we should use the export static method
459      * BUT this throws an exception at this point).
460      *
461      * @return string
462      * @codeCoverageIgnore Not yet implemented
463      */
464     public function __toString()
465     {
466         return 'Not yet implemented';
467     }
468 }