Mercurial > hg > isophonics-drupal-site
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 } |