comparison vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents
children
comparison
equal deleted inserted replaced
11:bfffd8d7479a 12:7a779792577d
1 <?php
2 /**
3 * This file is part of phpDocumentor.
4 *
5 * For the full copyright and license information, please view the LICENSE
6 * file that was distributed with this source code.
7 *
8 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
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\DescriptionFactory;
16 use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
17 use phpDocumentor\Reflection\DocBlock\Tag;
18 use phpDocumentor\Reflection\DocBlock\TagFactory;
19 use Webmozart\Assert\Assert;
20
21 final class DocBlockFactory implements DocBlockFactoryInterface
22 {
23 /** @var DocBlock\DescriptionFactory */
24 private $descriptionFactory;
25
26 /** @var DocBlock\TagFactory */
27 private $tagFactory;
28
29 /**
30 * Initializes this factory with the required subcontractors.
31 *
32 * @param DescriptionFactory $descriptionFactory
33 * @param TagFactory $tagFactory
34 */
35 public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory)
36 {
37 $this->descriptionFactory = $descriptionFactory;
38 $this->tagFactory = $tagFactory;
39 }
40
41 /**
42 * Factory method for easy instantiation.
43 *
44 * @param string[] $additionalTags
45 *
46 * @return DocBlockFactory
47 */
48 public static function createInstance(array $additionalTags = [])
49 {
50 $fqsenResolver = new FqsenResolver();
51 $tagFactory = new StandardTagFactory($fqsenResolver);
52 $descriptionFactory = new DescriptionFactory($tagFactory);
53
54 $tagFactory->addService($descriptionFactory);
55 $tagFactory->addService(new TypeResolver($fqsenResolver));
56
57 $docBlockFactory = new self($descriptionFactory, $tagFactory);
58 foreach ($additionalTags as $tagName => $tagHandler) {
59 $docBlockFactory->registerTagHandler($tagName, $tagHandler);
60 }
61
62 return $docBlockFactory;
63 }
64
65 /**
66 * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the
67 * getDocComment method (such as a ReflectionClass object).
68 * @param Types\Context $context
69 * @param Location $location
70 *
71 * @return DocBlock
72 */
73 public function create($docblock, Types\Context $context = null, Location $location = null)
74 {
75 if (is_object($docblock)) {
76 if (!method_exists($docblock, 'getDocComment')) {
77 $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
78 throw new \InvalidArgumentException($exceptionMessage);
79 }
80
81 $docblock = $docblock->getDocComment();
82 }
83
84 Assert::stringNotEmpty($docblock);
85
86 if ($context === null) {
87 $context = new Types\Context('');
88 }
89
90 $parts = $this->splitDocBlock($this->stripDocComment($docblock));
91 list($templateMarker, $summary, $description, $tags) = $parts;
92
93 return new DocBlock(
94 $summary,
95 $description ? $this->descriptionFactory->create($description, $context) : null,
96 array_filter($this->parseTagBlock($tags, $context), function ($tag) {
97 return $tag instanceof Tag;
98 }),
99 $context,
100 $location,
101 $templateMarker === '#@+',
102 $templateMarker === '#@-'
103 );
104 }
105
106 public function registerTagHandler($tagName, $handler)
107 {
108 $this->tagFactory->registerTagHandler($tagName, $handler);
109 }
110
111 /**
112 * Strips the asterisks from the DocBlock comment.
113 *
114 * @param string $comment String containing the comment text.
115 *
116 * @return string
117 */
118 private function stripDocComment($comment)
119 {
120 $comment = trim(preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', '$1', $comment));
121
122 // reg ex above is not able to remove */ from a single line docblock
123 if (substr($comment, -2) === '*/') {
124 $comment = trim(substr($comment, 0, -2));
125 }
126
127 return str_replace(["\r\n", "\r"], "\n", $comment);
128 }
129
130 /**
131 * Splits the DocBlock into a template marker, summary, description and block of tags.
132 *
133 * @param string $comment Comment to split into the sub-parts.
134 *
135 * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
136 * @author Mike van Riel <me@mikevanriel.com> for extending the regex with template marker support.
137 *
138 * @return string[] containing the template marker (if any), summary, description and a string containing the tags.
139 */
140 private function splitDocBlock($comment)
141 {
142 // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
143 // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
144 // performance impact of running a regular expression
145 if (strpos($comment, '@') === 0) {
146 return ['', '', '', $comment];
147 }
148
149 // clears all extra horizontal whitespace from the line endings to prevent parsing issues
150 $comment = preg_replace('/\h*$/Sum', '', $comment);
151
152 /*
153 * Splits the docblock into a template marker, summary, description and tags section.
154 *
155 * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
156 * occur after it and will be stripped).
157 * - The short description is started from the first character until a dot is encountered followed by a
158 * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
159 * errors). This is optional.
160 * - The long description, any character until a new line is encountered followed by an @ and word
161 * characters (a tag). This is optional.
162 * - Tags; the remaining characters
163 *
164 * Big thanks to RichardJ for contributing this Regular Expression
165 */
166 preg_match(
167 '/
168 \A
169 # 1. Extract the template marker
170 (?:(\#\@\+|\#\@\-)\n?)?
171
172 # 2. Extract the summary
173 (?:
174 (?! @\pL ) # The summary may not start with an @
175 (
176 [^\n.]+
177 (?:
178 (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines
179 [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
180 [^\n.]+ # Include anything else
181 )*
182 \.?
183 )?
184 )
185
186 # 3. Extract the description
187 (?:
188 \s* # Some form of whitespace _must_ precede a description because a summary must be there
189 (?! @\pL ) # The description may not start with an @
190 (
191 [^\n]+
192 (?: \n+
193 (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
194 [^\n]+ # Include anything else
195 )*
196 )
197 )?
198
199 # 4. Extract the tags (anything that follows)
200 (\s+ [\s\S]*)? # everything that follows
201 /ux',
202 $comment,
203 $matches
204 );
205 array_shift($matches);
206
207 while (count($matches) < 4) {
208 $matches[] = '';
209 }
210
211 return $matches;
212 }
213
214 /**
215 * Creates the tag objects.
216 *
217 * @param string $tags Tag block to parse.
218 * @param Types\Context $context Context of the parsed Tag
219 *
220 * @return DocBlock\Tag[]
221 */
222 private function parseTagBlock($tags, Types\Context $context)
223 {
224 $tags = $this->filterTagBlock($tags);
225 if (!$tags) {
226 return [];
227 }
228
229 $result = $this->splitTagBlockIntoTagLines($tags);
230 foreach ($result as $key => $tagLine) {
231 $result[$key] = $this->tagFactory->create(trim($tagLine), $context);
232 }
233
234 return $result;
235 }
236
237 /**
238 * @param string $tags
239 *
240 * @return string[]
241 */
242 private function splitTagBlockIntoTagLines($tags)
243 {
244 $result = [];
245 foreach (explode("\n", $tags) as $tag_line) {
246 if (isset($tag_line[0]) && ($tag_line[0] === '@')) {
247 $result[] = $tag_line;
248 } else {
249 $result[count($result) - 1] .= "\n" . $tag_line;
250 }
251 }
252
253 return $result;
254 }
255
256 /**
257 * @param $tags
258 * @return string
259 */
260 private function filterTagBlock($tags)
261 {
262 $tags = trim($tags);
263 if (!$tags) {
264 return null;
265 }
266
267 if ('@' !== $tags[0]) {
268 // @codeCoverageIgnoreStart
269 // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that
270 // we didn't foresee.
271 throw new \LogicException('A tag block started with text instead of an at-sign(@): ' . $tags);
272 // @codeCoverageIgnoreEnd
273 }
274
275 return $tags;
276 }
277 }