Complex classes like CommentAnalyzer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use CommentAnalyzer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
49 | class CommentAnalyzer |
||
50 | { |
||
51 | const TYPE_REGEX = '(\??\\\?[\(\)A-Za-z0-9_&\<\.=,\>\[\]\-\{\}:|?\\\\]*|\$[a-zA-Z_0-9_]+)'; |
||
52 | |||
53 | /** |
||
54 | * @param array<string, array<string, array{Type\Union}>>|null $template_type_map |
||
55 | * @param array<string, TypeAlias> $type_aliases |
||
56 | * |
||
57 | * @throws DocblockParseException if there was a problem parsing the docblock |
||
58 | * |
||
59 | * @return VarDocblockComment[] |
||
60 | */ |
||
61 | public static function getTypeFromComment( |
||
62 | PhpParser\Comment\Doc $comment, |
||
63 | FileSource $source, |
||
64 | Aliases $aliases, |
||
65 | array $template_type_map = null, |
||
66 | ?array $type_aliases = null |
||
67 | ) { |
||
68 | $parsed_docblock = DocComment::parsePreservingLength($comment); |
||
69 | |||
70 | return self::arrayToDocblocks( |
||
71 | $comment, |
||
72 | $parsed_docblock, |
||
73 | $source, |
||
74 | $aliases, |
||
75 | $template_type_map, |
||
76 | $type_aliases |
||
77 | ); |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * @param array<string, array<string, array{Type\Union}>>|null $template_type_map |
||
82 | * @param array<string, TypeAlias> $type_aliases |
||
83 | * |
||
84 | * @return VarDocblockComment[] |
||
85 | * |
||
86 | * @throws DocblockParseException if there was a problem parsing the docblock |
||
87 | */ |
||
88 | public static function arrayToDocblocks( |
||
89 | PhpParser\Comment\Doc $comment, |
||
90 | ParsedDocblock $parsed_docblock, |
||
91 | FileSource $source, |
||
92 | Aliases $aliases, |
||
93 | array $template_type_map = null, |
||
94 | ?array $type_aliases = null |
||
95 | ) : array { |
||
96 | $var_id = null; |
||
97 | |||
98 | $var_type_tokens = null; |
||
99 | $original_type = null; |
||
100 | |||
101 | $var_comments = []; |
||
102 | |||
103 | $comment_text = $comment->getText(); |
||
104 | |||
105 | $var_line_number = $comment->getLine(); |
||
|
|||
106 | |||
107 | if (isset($parsed_docblock->combined_tags['var'])) { |
||
108 | foreach ($parsed_docblock->combined_tags['var'] as $offset => $var_line) { |
||
109 | $var_line = trim($var_line); |
||
110 | |||
111 | if (!$var_line) { |
||
112 | continue; |
||
113 | } |
||
114 | |||
115 | $type_start = null; |
||
116 | $type_end = null; |
||
117 | |||
118 | $line_parts = self::splitDocLine($var_line); |
||
119 | |||
120 | $line_number = $comment->getLine() + substr_count($comment_text, "\n", 0, $offset); |
||
121 | |||
122 | if ($line_parts && $line_parts[0]) { |
||
123 | $type_start = $offset + $comment->getFilePos(); |
||
124 | $type_end = $type_start + strlen($line_parts[0]); |
||
125 | |||
126 | $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); |
||
127 | |||
128 | if ($line_parts[0] === '' |
||
129 | || ($line_parts[0][0] === '$' |
||
130 | && !preg_match('/^\$this(\||$)/', $line_parts[0])) |
||
131 | ) { |
||
132 | throw new IncorrectDocblockException('Misplaced variable'); |
||
133 | } |
||
134 | |||
135 | try { |
||
136 | $var_type_tokens = TypeTokenizer::getFullyQualifiedTokens( |
||
137 | $line_parts[0], |
||
138 | $aliases, |
||
139 | $template_type_map, |
||
140 | $type_aliases |
||
141 | ); |
||
142 | } catch (TypeParseTreeException $e) { |
||
143 | throw new DocblockParseException($line_parts[0] . ' is not a valid type'); |
||
144 | } |
||
145 | |||
146 | $original_type = $line_parts[0]; |
||
147 | |||
148 | $var_line_number = $line_number; |
||
149 | |||
150 | if (count($line_parts) > 1 && $line_parts[1][0] === '$') { |
||
151 | $var_id = $line_parts[1]; |
||
152 | } |
||
153 | } |
||
154 | |||
155 | if (!$var_type_tokens || !$original_type) { |
||
156 | continue; |
||
157 | } |
||
158 | |||
159 | try { |
||
160 | $defined_type = TypeParser::parseTokens( |
||
161 | $var_type_tokens, |
||
162 | null, |
||
163 | $template_type_map ?: [], |
||
164 | $type_aliases ?: [] |
||
165 | ); |
||
166 | } catch (TypeParseTreeException $e) { |
||
167 | throw new DocblockParseException( |
||
168 | $line_parts[0] . |
||
169 | ' is not a valid type' . |
||
170 | ' (from ' . |
||
171 | $source->getFilePath() . |
||
172 | ':' . |
||
173 | $comment->getLine() . |
||
174 | ')' |
||
175 | ); |
||
176 | } |
||
177 | |||
178 | $defined_type->setFromDocblock(); |
||
179 | |||
180 | $var_comment = new VarDocblockComment(); |
||
181 | $var_comment->type = $defined_type; |
||
182 | $var_comment->original_type = $original_type; |
||
183 | $var_comment->var_id = $var_id; |
||
184 | $var_comment->line_number = $var_line_number; |
||
185 | $var_comment->type_start = $type_start; |
||
186 | $var_comment->type_end = $type_end; |
||
187 | |||
188 | self::decorateVarDocblockComment($var_comment, $parsed_docblock); |
||
189 | |||
190 | $var_comments[] = $var_comment; |
||
191 | } |
||
192 | } |
||
193 | |||
194 | if (!$var_comments |
||
195 | && (isset($parsed_docblock->tags['deprecated']) |
||
196 | || isset($parsed_docblock->tags['internal']) |
||
197 | || isset($parsed_docblock->tags['readonly']) |
||
198 | || isset($parsed_docblock->tags['psalm-readonly']) |
||
199 | || isset($parsed_docblock->tags['psalm-readonly-allow-private-mutation']) |
||
200 | || isset($parsed_docblock->tags['psalm-taint-escape']) |
||
201 | || isset($parsed_docblock->tags['psalm-internal'])) |
||
202 | ) { |
||
203 | $var_comment = new VarDocblockComment(); |
||
204 | |||
205 | self::decorateVarDocblockComment($var_comment, $parsed_docblock); |
||
206 | |||
207 | $var_comments[] = $var_comment; |
||
208 | } |
||
209 | |||
210 | return $var_comments; |
||
211 | } |
||
212 | |||
213 | private static function decorateVarDocblockComment( |
||
214 | VarDocblockComment $var_comment, |
||
215 | ParsedDocblock $parsed_docblock |
||
216 | ) : void { |
||
217 | $var_comment->deprecated = isset($parsed_docblock->tags['deprecated']); |
||
218 | $var_comment->internal = isset($parsed_docblock->tags['internal']); |
||
219 | $var_comment->readonly = isset($parsed_docblock->tags['readonly']) |
||
220 | || isset($parsed_docblock->tags['psalm-readonly']) |
||
221 | || isset($parsed_docblock->tags['psalm-readonly-allow-private-mutation']); |
||
222 | |||
223 | $var_comment->allow_private_mutation |
||
224 | = isset($parsed_docblock->tags['psalm-allow-private-mutation']) |
||
225 | || isset($parsed_docblock->tags['psalm-readonly-allow-private-mutation']); |
||
226 | |||
227 | if (isset($parsed_docblock->tags['psalm-taint-escape'])) { |
||
228 | foreach ($parsed_docblock->tags['psalm-taint-escape'] as $param) { |
||
229 | $param = trim($param); |
||
230 | $var_comment->removed_taints[] = $param; |
||
231 | } |
||
232 | } |
||
233 | |||
234 | if (isset($parsed_docblock->tags['psalm-internal'])) { |
||
235 | $psalm_internal = reset($parsed_docblock->tags['psalm-internal']); |
||
236 | |||
237 | if (!$psalm_internal) { |
||
238 | throw new DocblockParseException('psalm-internal annotation used without specifying namespace'); |
||
239 | } |
||
240 | |||
241 | $var_comment->psalm_internal = reset($parsed_docblock->tags['psalm-internal']); |
||
242 | |||
243 | if (!$var_comment->internal) { |
||
244 | throw new DocblockParseException('@psalm-internal annotation used without @internal'); |
||
245 | } |
||
246 | } |
||
247 | } |
||
248 | |||
249 | private static function sanitizeDocblockType(string $docblock_type) : string |
||
250 | { |
||
251 | $docblock_type = preg_replace('@^[ \t]*\*@m', '', $docblock_type); |
||
252 | $docblock_type = preg_replace('/,\n\s+\}/', '}', $docblock_type); |
||
253 | return str_replace("\n", '', $docblock_type); |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * @param Aliases $aliases |
||
258 | * @param array<string, TypeAlias> $type_aliases |
||
259 | * |
||
260 | * @throws DocblockParseException if there was a problem parsing the docblock |
||
261 | * |
||
262 | * @return array<string, TypeAlias\InlineTypeAlias> |
||
263 | */ |
||
264 | public static function getTypeAliasesFromComment( |
||
265 | PhpParser\Comment\Doc $comment, |
||
266 | Aliases $aliases, |
||
267 | ?array $type_aliases, |
||
268 | ?string $self_fqcln |
||
269 | ) { |
||
270 | $parsed_docblock = DocComment::parsePreservingLength($comment); |
||
271 | |||
272 | if (!isset($parsed_docblock->tags['psalm-type'])) { |
||
273 | return []; |
||
274 | } |
||
275 | |||
276 | return self::getTypeAliasesFromCommentLines( |
||
277 | $parsed_docblock->tags['psalm-type'], |
||
278 | $aliases, |
||
279 | $type_aliases, |
||
280 | $self_fqcln |
||
281 | ); |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * @param array<string> $type_alias_comment_lines |
||
286 | * @param Aliases $aliases |
||
287 | * @param array<string, TypeAlias> $type_aliases |
||
288 | * |
||
289 | * @throws DocblockParseException if there was a problem parsing the docblock |
||
290 | * |
||
291 | * @return array<string, TypeAlias\InlineTypeAlias> |
||
292 | */ |
||
293 | private static function getTypeAliasesFromCommentLines( |
||
294 | array $type_alias_comment_lines, |
||
295 | Aliases $aliases, |
||
296 | ?array $type_aliases, |
||
297 | ?string $self_fqcln |
||
298 | ) { |
||
299 | $type_alias_tokens = []; |
||
300 | |||
301 | foreach ($type_alias_comment_lines as $var_line) { |
||
302 | $var_line = trim($var_line); |
||
303 | |||
304 | if (!$var_line) { |
||
305 | continue; |
||
306 | } |
||
307 | |||
308 | $var_line = preg_replace('/[ \t]+/', ' ', preg_replace('@^[ \t]*\*@m', '', $var_line)); |
||
309 | $var_line = preg_replace('/,\n\s+\}/', '}', $var_line); |
||
310 | $var_line = str_replace("\n", '', $var_line); |
||
311 | |||
312 | $var_line_parts = preg_split('/( |=)/', $var_line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); |
||
313 | |||
314 | if (!$var_line_parts) { |
||
315 | continue; |
||
316 | } |
||
317 | |||
318 | $type_alias = array_shift($var_line_parts); |
||
319 | |||
320 | if (!isset($var_line_parts[0])) { |
||
321 | continue; |
||
322 | } |
||
323 | |||
324 | if ($var_line_parts[0] === ' ') { |
||
325 | array_shift($var_line_parts); |
||
326 | } |
||
327 | |||
328 | if ($var_line_parts[0] === '=') { |
||
329 | array_shift($var_line_parts); |
||
330 | } |
||
331 | |||
332 | if (!isset($var_line_parts[0])) { |
||
333 | continue; |
||
334 | } |
||
335 | |||
336 | if ($var_line_parts[0] === ' ') { |
||
337 | array_shift($var_line_parts); |
||
338 | } |
||
339 | |||
340 | $type_string = str_replace("\n", '', implode('', $var_line_parts)); |
||
341 | |||
342 | $type_string = preg_replace('/>[^>^\}]*$/', '>', $type_string); |
||
343 | $type_string = preg_replace('/\}[^>^\}]*$/', '}', $type_string); |
||
344 | |||
345 | try { |
||
346 | $type_tokens = TypeTokenizer::getFullyQualifiedTokens( |
||
347 | $type_string, |
||
348 | $aliases, |
||
349 | null, |
||
350 | $type_alias_tokens + $type_aliases, |
||
351 | $self_fqcln |
||
352 | ); |
||
353 | } catch (TypeParseTreeException $e) { |
||
354 | throw new DocblockParseException($type_string . ' is not a valid type'); |
||
355 | } |
||
356 | |||
357 | $type_alias_tokens[$type_alias] = new TypeAlias\InlineTypeAlias($type_tokens); |
||
358 | } |
||
359 | |||
360 | return $type_alias_tokens; |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * @param int $line_number |
||
365 | * |
||
366 | * @throws DocblockParseException if there was a problem parsing the docblock |
||
367 | * |
||
368 | * @return FunctionDocblockComment |
||
369 | */ |
||
370 | public static function extractFunctionDocblockInfo(PhpParser\Comment\Doc $comment) |
||
371 | { |
||
372 | $parsed_docblock = DocComment::parsePreservingLength($comment); |
||
373 | |||
374 | $comment_text = $comment->getText(); |
||
375 | |||
376 | $info = new FunctionDocblockComment(); |
||
377 | |||
378 | self::checkDuplicatedTags($parsed_docblock); |
||
379 | |||
380 | if (isset($parsed_docblock->combined_tags['return'])) { |
||
381 | self::extractReturnType( |
||
382 | $comment, |
||
383 | $parsed_docblock->combined_tags['return'], |
||
384 | $info |
||
385 | ); |
||
386 | } |
||
387 | |||
388 | if (isset($parsed_docblock->combined_tags['param'])) { |
||
389 | foreach ($parsed_docblock->combined_tags['param'] as $offset => $param) { |
||
390 | $line_parts = self::splitDocLine($param); |
||
391 | |||
392 | if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') { |
||
393 | continue; |
||
394 | } |
||
395 | |||
396 | if (count($line_parts) > 1) { |
||
397 | if (preg_match('/^&?(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1]) |
||
398 | && $line_parts[0][0] !== '{' |
||
399 | ) { |
||
400 | $line_parts[1] = str_replace('&', '', $line_parts[1]); |
||
401 | |||
402 | $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); |
||
403 | |||
404 | $start = $offset + $comment->getFilePos(); |
||
405 | $end = $start + strlen($line_parts[0]); |
||
406 | |||
407 | $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); |
||
408 | |||
409 | if ($line_parts[0] === '' |
||
410 | || ($line_parts[0][0] === '$' |
||
411 | && !preg_match('/^\$this(\||$)/', $line_parts[0])) |
||
412 | ) { |
||
413 | throw new IncorrectDocblockException('Misplaced variable'); |
||
414 | } |
||
415 | |||
416 | $info->params[] = [ |
||
417 | 'name' => trim($line_parts[1]), |
||
418 | 'type' => $line_parts[0], |
||
419 | 'line_number' => $comment->getLine() + substr_count($comment_text, "\n", 0, $offset), |
||
420 | 'start' => $start, |
||
421 | 'end' => $end, |
||
422 | ]; |
||
423 | } |
||
424 | } else { |
||
425 | throw new DocblockParseException('Badly-formatted @param'); |
||
426 | } |
||
427 | } |
||
428 | } |
||
429 | |||
430 | if (isset($parsed_docblock->tags['param-out'])) { |
||
431 | foreach ($parsed_docblock->tags['param-out'] as $offset => $param) { |
||
432 | $line_parts = self::splitDocLine($param); |
||
433 | |||
434 | if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') { |
||
435 | continue; |
||
436 | } |
||
437 | |||
438 | if (count($line_parts) > 1) { |
||
439 | if (!preg_match('/\[[^\]]+\]/', $line_parts[0]) |
||
440 | && preg_match('/^(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1]) |
||
441 | && $line_parts[0][0] !== '{' |
||
442 | ) { |
||
443 | if ($line_parts[1][0] === '&') { |
||
444 | $line_parts[1] = substr($line_parts[1], 1); |
||
445 | } |
||
446 | |||
447 | $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); |
||
448 | |||
449 | if ($line_parts[0] === '' |
||
450 | || ($line_parts[0][0] === '$' |
||
451 | && !preg_match('/^\$this(\||$)/', $line_parts[0])) |
||
452 | ) { |
||
453 | throw new IncorrectDocblockException('Misplaced variable'); |
||
454 | } |
||
455 | |||
456 | $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); |
||
457 | |||
458 | $info->params_out[] = [ |
||
459 | 'name' => trim($line_parts[1]), |
||
460 | 'type' => str_replace("\n", '', $line_parts[0]), |
||
461 | 'line_number' => $comment->getLine() + substr_count($comment_text, "\n", 0, $offset), |
||
462 | ]; |
||
463 | } |
||
464 | } else { |
||
465 | throw new DocblockParseException('Badly-formatted @param'); |
||
466 | } |
||
467 | } |
||
468 | } |
||
469 | |||
470 | if (isset($parsed_docblock->tags['psalm-self-out'])) { |
||
471 | foreach ($parsed_docblock->tags['psalm-self-out'] as $offset => $param) { |
||
472 | $line_parts = self::splitDocLine($param); |
||
473 | |||
474 | if (count($line_parts) > 0) { |
||
475 | $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); |
||
476 | |||
477 | $info->self_out = [ |
||
478 | 'type' => str_replace("\n", '', $line_parts[0]), |
||
479 | 'line_number' => $comment->getLine() + substr_count($comment_text, "\n", 0, $offset), |
||
480 | ]; |
||
481 | } |
||
482 | } |
||
483 | } |
||
484 | |||
485 | if (isset($parsed_docblock->tags['psalm-flow'])) { |
||
486 | foreach ($parsed_docblock->tags['psalm-flow'] as $param) { |
||
487 | $info->flows[] = trim($param); |
||
488 | } |
||
489 | } |
||
490 | |||
491 | if (isset($parsed_docblock->tags['psalm-taint-sink'])) { |
||
492 | foreach ($parsed_docblock->tags['psalm-taint-sink'] as $param) { |
||
493 | $param_parts = preg_split('/\s+/', trim($param)); |
||
494 | |||
495 | if (count($param_parts) === 2) { |
||
496 | $info->taint_sink_params[] = ['name' => $param_parts[1], 'taint' => $param_parts[0]]; |
||
497 | } |
||
498 | } |
||
499 | } |
||
500 | |||
501 | // support for MediaWiki taint plugin |
||
502 | if (isset($parsed_docblock->tags['param-taint'])) { |
||
503 | foreach ($parsed_docblock->tags['param-taint'] as $param) { |
||
504 | $param_parts = preg_split('/\s+/', trim($param)); |
||
505 | |||
506 | if (count($param_parts) === 2) { |
||
507 | $taint_type = $param_parts[1]; |
||
508 | |||
509 | if (substr($taint_type, 0, 5) === 'exec_') { |
||
510 | $taint_type = substr($taint_type, 5); |
||
511 | |||
512 | if ($taint_type === 'tainted') { |
||
513 | $taint_type = 'input'; |
||
514 | } |
||
515 | |||
516 | if ($taint_type === 'misc') { |
||
517 | $taint_type = 'text'; |
||
518 | } |
||
519 | |||
520 | $info->taint_sink_params[] = ['name' => $param_parts[0], 'taint' => $taint_type]; |
||
521 | } |
||
522 | } |
||
523 | } |
||
524 | } |
||
525 | |||
526 | if (isset($parsed_docblock->tags['psalm-taint-source'])) { |
||
527 | foreach ($parsed_docblock->tags['psalm-taint-source'] as $param) { |
||
528 | $param_parts = preg_split('/\s+/', trim($param)); |
||
529 | |||
530 | if ($param_parts[0]) { |
||
531 | $info->taint_source_types[] = $param_parts[0]; |
||
532 | } |
||
533 | } |
||
534 | } elseif (isset($parsed_docblock->tags['return-taint'])) { |
||
535 | // support for MediaWiki taint plugin |
||
536 | foreach ($parsed_docblock->tags['return-taint'] as $param) { |
||
537 | $param_parts = preg_split('/\s+/', trim($param)); |
||
538 | |||
539 | if ($param_parts[0]) { |
||
540 | if ($param_parts[0] === 'tainted') { |
||
541 | $param_parts[0] = 'input'; |
||
542 | } |
||
543 | |||
544 | if ($param_parts[0] === 'misc') { |
||
545 | $param_parts[0] = 'text'; |
||
546 | } |
||
547 | |||
548 | if ($param_parts[0] !== 'none') { |
||
549 | $info->taint_source_types[] = $param_parts[0]; |
||
550 | } |
||
551 | } |
||
552 | } |
||
553 | } |
||
554 | |||
555 | if (isset($parsed_docblock->tags['psalm-taint-unescape'])) { |
||
556 | foreach ($parsed_docblock->tags['psalm-taint-unescape'] as $param) { |
||
557 | $param = trim($param); |
||
558 | $info->added_taints[] = $param; |
||
559 | } |
||
560 | } |
||
561 | |||
562 | if (isset($parsed_docblock->tags['psalm-taint-escape'])) { |
||
563 | foreach ($parsed_docblock->tags['psalm-taint-escape'] as $param) { |
||
564 | $param = trim($param); |
||
565 | $info->removed_taints[] = $param; |
||
566 | } |
||
567 | } |
||
568 | |||
569 | if (isset($parsed_docblock->tags['psalm-assert-untainted'])) { |
||
570 | foreach ($parsed_docblock->tags['psalm-assert-untainted'] as $param) { |
||
571 | $param = trim($param); |
||
572 | |||
573 | $info->assert_untainted_params[] = ['name' => $param]; |
||
574 | } |
||
575 | } |
||
576 | |||
577 | if (isset($parsed_docblock->tags['psalm-taint-specialize'])) { |
||
578 | $info->specialize_call = true; |
||
579 | } |
||
580 | |||
581 | if (isset($parsed_docblock->tags['global'])) { |
||
582 | foreach ($parsed_docblock->tags['global'] as $offset => $global) { |
||
583 | $line_parts = self::splitDocLine($global); |
||
584 | |||
585 | if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') { |
||
586 | continue; |
||
587 | } |
||
588 | |||
589 | if (count($line_parts) > 1) { |
||
590 | if (!preg_match('/\[[^\]]+\]/', $line_parts[0]) |
||
591 | && preg_match('/^(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1]) |
||
592 | && $line_parts[0][0] !== '{' |
||
593 | ) { |
||
594 | if ($line_parts[1][0] === '&') { |
||
595 | $line_parts[1] = substr($line_parts[1], 1); |
||
596 | } |
||
597 | |||
598 | if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) { |
||
599 | throw new IncorrectDocblockException('Misplaced variable'); |
||
600 | } |
||
601 | |||
602 | $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); |
||
603 | |||
604 | $info->globals[] = [ |
||
605 | 'name' => $line_parts[1], |
||
606 | 'type' => $line_parts[0], |
||
607 | 'line_number' => $comment->getLine() + substr_count($comment_text, "\n", 0, $offset), |
||
608 | ]; |
||
609 | } |
||
610 | } else { |
||
611 | throw new DocblockParseException('Badly-formatted @param'); |
||
612 | } |
||
613 | } |
||
614 | } |
||
615 | |||
616 | if (isset($parsed_docblock->tags['deprecated'])) { |
||
617 | $info->deprecated = true; |
||
618 | } |
||
619 | |||
620 | if (isset($parsed_docblock->tags['internal'])) { |
||
621 | $info->internal = true; |
||
622 | } |
||
623 | |||
624 | if (isset($parsed_docblock->tags['psalm-internal'])) { |
||
625 | $psalm_internal = reset($parsed_docblock->tags['psalm-internal']); |
||
626 | if ($psalm_internal) { |
||
627 | $info->psalm_internal = $psalm_internal; |
||
628 | } else { |
||
629 | throw new DocblockParseException('@psalm-internal annotation used without specifying namespace'); |
||
630 | } |
||
631 | $info->psalm_internal = reset($parsed_docblock->tags['psalm-internal']); |
||
632 | |||
633 | if (! $info->internal) { |
||
634 | throw new DocblockParseException('@psalm-internal annotation used without @internal'); |
||
635 | } |
||
636 | } |
||
637 | |||
638 | if (isset($parsed_docblock->tags['psalm-suppress'])) { |
||
639 | foreach ($parsed_docblock->tags['psalm-suppress'] as $offset => $suppress_entry) { |
||
640 | $info->suppressed_issues[$offset + $comment->getFilePos()] = preg_split('/[\s]+/', $suppress_entry)[0]; |
||
641 | } |
||
642 | } |
||
643 | |||
644 | if (isset($parsed_docblock->tags['throws'])) { |
||
645 | foreach ($parsed_docblock->tags['throws'] as $offset => $throws_entry) { |
||
646 | $throws_class = preg_split('/[\s]+/', $throws_entry)[0]; |
||
647 | |||
648 | if (!$throws_class) { |
||
649 | throw new IncorrectDocblockException('Unexpectedly empty @throws'); |
||
650 | } |
||
651 | |||
652 | $info->throws[] = [ |
||
653 | $throws_class, |
||
654 | $offset + $comment->getFilePos(), |
||
655 | $comment->getLine() + substr_count($comment->getText(), "\n", 0, $offset) |
||
656 | ]; |
||
657 | } |
||
658 | } |
||
659 | |||
660 | if (strpos(strtolower($parsed_docblock->description), '@inheritdoc') !== false |
||
661 | || isset($parsed_docblock->tags['inheritdoc']) |
||
662 | || isset($parsed_docblock->tags['inheritDoc']) |
||
663 | ) { |
||
664 | $info->inheritdoc = true; |
||
665 | } |
||
666 | |||
667 | if (isset($parsed_docblock->combined_tags['template'])) { |
||
668 | foreach ($parsed_docblock->combined_tags['template'] as $template_line) { |
||
669 | $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); |
||
670 | |||
671 | $template_name = array_shift($template_type); |
||
672 | |||
673 | if (!$template_name) { |
||
674 | throw new IncorrectDocblockException('Empty @template tag'); |
||
675 | } |
||
676 | |||
677 | if (count($template_type) > 1 |
||
678 | && in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true) |
||
679 | ) { |
||
680 | $template_modifier = strtolower(array_shift($template_type)); |
||
681 | $info->templates[] = [ |
||
682 | $template_name, |
||
683 | $template_modifier, |
||
684 | implode(' ', $template_type), |
||
685 | false |
||
686 | ]; |
||
687 | } else { |
||
688 | $info->templates[] = [$template_name, null, null, false]; |
||
689 | } |
||
690 | } |
||
691 | } |
||
692 | |||
693 | if (isset($parsed_docblock->tags['template-typeof'])) { |
||
694 | foreach ($parsed_docblock->tags['template-typeof'] as $template_typeof) { |
||
695 | $typeof_parts = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_typeof)); |
||
696 | |||
697 | if ($typeof_parts === false || count($typeof_parts) < 2 || $typeof_parts[1][0] !== '$') { |
||
698 | throw new IncorrectDocblockException('Misplaced variable'); |
||
699 | } |
||
700 | |||
701 | $info->template_typeofs[] = [ |
||
702 | 'template_type' => $typeof_parts[0], |
||
703 | 'param_name' => substr($typeof_parts[1], 1), |
||
704 | ]; |
||
705 | } |
||
706 | } |
||
707 | |||
708 | if (isset($parsed_docblock->tags['psalm-assert'])) { |
||
709 | foreach ($parsed_docblock->tags['psalm-assert'] as $assertion) { |
||
710 | $line_parts = self::splitDocLine($assertion); |
||
711 | |||
712 | if (count($line_parts) < 2 || $line_parts[1][0] !== '$') { |
||
713 | throw new IncorrectDocblockException('Misplaced variable'); |
||
714 | } |
||
715 | |||
716 | $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); |
||
717 | |||
718 | $info->assertions[] = [ |
||
719 | 'type' => $line_parts[0], |
||
720 | 'param_name' => substr($line_parts[1], 1), |
||
721 | ]; |
||
722 | } |
||
723 | } |
||
724 | |||
725 | if (isset($parsed_docblock->tags['psalm-assert-if-true'])) { |
||
726 | foreach ($parsed_docblock->tags['psalm-assert-if-true'] as $assertion) { |
||
727 | $line_parts = self::splitDocLine($assertion); |
||
728 | |||
729 | if (count($line_parts) < 2 || $line_parts[1][0] !== '$') { |
||
730 | throw new IncorrectDocblockException('Misplaced variable'); |
||
731 | } |
||
732 | |||
733 | $info->if_true_assertions[] = [ |
||
734 | 'type' => $line_parts[0], |
||
735 | 'param_name' => substr($line_parts[1], 1), |
||
736 | ]; |
||
737 | } |
||
738 | } |
||
739 | |||
740 | if (isset($parsed_docblock->tags['psalm-assert-if-false'])) { |
||
741 | foreach ($parsed_docblock->tags['psalm-assert-if-false'] as $assertion) { |
||
742 | $line_parts = self::splitDocLine($assertion); |
||
743 | |||
744 | if (count($line_parts) < 2 || $line_parts[1][0] !== '$') { |
||
745 | throw new IncorrectDocblockException('Misplaced variable'); |
||
746 | } |
||
747 | |||
748 | $info->if_false_assertions[] = [ |
||
749 | 'type' => $line_parts[0], |
||
750 | 'param_name' => substr($line_parts[1], 1), |
||
751 | ]; |
||
752 | } |
||
753 | } |
||
754 | |||
755 | $info->variadic = isset($parsed_docblock->tags['psalm-variadic']); |
||
756 | $info->pure = isset($parsed_docblock->tags['psalm-pure']) |
||
757 | || isset($parsed_docblock->tags['pure']); |
||
758 | |||
759 | if (isset($parsed_docblock->tags['psalm-mutation-free'])) { |
||
760 | $info->mutation_free = true; |
||
761 | } |
||
762 | |||
763 | if (isset($parsed_docblock->tags['psalm-external-mutation-free'])) { |
||
764 | $info->external_mutation_free = true; |
||
765 | } |
||
766 | |||
767 | $info->ignore_nullable_return = isset($parsed_docblock->tags['psalm-ignore-nullable-return']); |
||
768 | $info->ignore_falsable_return = isset($parsed_docblock->tags['psalm-ignore-falsable-return']); |
||
769 | |||
770 | return $info; |
||
771 | } |
||
772 | |||
773 | /** |
||
774 | * @param array<int, string> $return_specials |
||
775 | * @return void |
||
776 | */ |
||
777 | private static function extractReturnType( |
||
778 | PhpParser\Comment\Doc $comment, |
||
779 | array $return_specials, |
||
780 | FunctionDocblockComment $info |
||
781 | ) { |
||
782 | foreach ($return_specials as $offset => $return_block) { |
||
783 | $return_lines = explode("\n", $return_block); |
||
784 | |||
785 | if (!trim($return_lines[0])) { |
||
786 | return; |
||
787 | } |
||
788 | |||
789 | $return_block = trim($return_block); |
||
790 | |||
791 | if (!$return_block) { |
||
792 | return; |
||
793 | } |
||
794 | |||
795 | $line_parts = self::splitDocLine($return_block); |
||
796 | |||
797 | if ($line_parts[0][0] !== '{') { |
||
798 | if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) { |
||
799 | throw new IncorrectDocblockException('Misplaced variable'); |
||
800 | } |
||
801 | |||
802 | $start = $offset + $comment->getFilePos(); |
||
803 | $end = $start + strlen($line_parts[0]); |
||
804 | |||
805 | $line_parts[0] = self::sanitizeDocblockType($line_parts[0]); |
||
806 | |||
807 | $info->return_type = array_shift($line_parts); |
||
808 | $info->return_type_description = $line_parts ? implode(' ', $line_parts) : null; |
||
809 | |||
810 | $info->return_type_line_number |
||
811 | = $comment->getLine() + substr_count($comment->getText(), "\n", 0, $offset); |
||
812 | $info->return_type_start = $start; |
||
813 | $info->return_type_end = $end; |
||
814 | } else { |
||
815 | throw new DocblockParseException('Badly-formatted @return type'); |
||
816 | } |
||
817 | |||
818 | break; |
||
819 | } |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * @throws DocblockParseException if there was a problem parsing the docblock |
||
824 | * |
||
825 | * @return ClassLikeDocblockComment |
||
826 | * @psalm-suppress MixedArrayAccess |
||
827 | */ |
||
828 | public static function extractClassLikeDocblockInfo( |
||
829 | \PhpParser\Node $node, |
||
830 | PhpParser\Comment\Doc $comment, |
||
831 | Aliases $aliases |
||
832 | ) { |
||
833 | $parsed_docblock = DocComment::parsePreservingLength($comment); |
||
834 | $codebase = ProjectAnalyzer::getInstance()->getCodebase(); |
||
835 | |||
836 | $info = new ClassLikeDocblockComment(); |
||
837 | |||
838 | if (isset($parsed_docblock->combined_tags['template'])) { |
||
839 | foreach ($parsed_docblock->combined_tags['template'] as $offset => $template_line) { |
||
840 | $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); |
||
841 | |||
842 | $template_name = array_shift($template_type); |
||
843 | |||
844 | if (!$template_name) { |
||
845 | throw new IncorrectDocblockException('Empty @template tag'); |
||
846 | } |
||
847 | |||
848 | if (count($template_type) > 1 |
||
849 | && in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true) |
||
850 | ) { |
||
851 | $template_modifier = strtolower(array_shift($template_type)); |
||
852 | $info->templates[] = [ |
||
853 | $template_name, |
||
854 | $template_modifier, |
||
855 | implode(' ', $template_type), |
||
856 | false, |
||
857 | $offset |
||
858 | ]; |
||
859 | } else { |
||
860 | $info->templates[] = [$template_name, null, null, false, $offset]; |
||
861 | } |
||
862 | } |
||
863 | } |
||
864 | |||
865 | if (isset($parsed_docblock->combined_tags['template-covariant'])) { |
||
866 | foreach ($parsed_docblock->combined_tags['template-covariant'] as $offset => $template_line) { |
||
867 | $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); |
||
868 | |||
869 | $template_name = array_shift($template_type); |
||
870 | |||
871 | if (!$template_name) { |
||
872 | throw new IncorrectDocblockException('Empty @template-covariant tag'); |
||
873 | } |
||
874 | |||
875 | if (count($template_type) > 1 |
||
876 | && in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true) |
||
877 | ) { |
||
878 | $template_modifier = strtolower(array_shift($template_type)); |
||
879 | $info->templates[] = [ |
||
880 | $template_name, |
||
881 | $template_modifier, |
||
882 | implode(' ', $template_type), |
||
883 | true, |
||
884 | $offset |
||
885 | ]; |
||
886 | } else { |
||
887 | $info->templates[] = [$template_name, null, null, true, $offset]; |
||
888 | } |
||
889 | } |
||
890 | } |
||
891 | |||
892 | if (isset($parsed_docblock->combined_tags['extends'])) { |
||
893 | foreach ($parsed_docblock->combined_tags['extends'] as $template_line) { |
||
894 | $info->template_extends[] = trim(preg_replace('@^[ \t]*\*@m', '', $template_line)); |
||
895 | } |
||
896 | } |
||
897 | |||
898 | if (isset($parsed_docblock->combined_tags['implements'])) { |
||
899 | foreach ($parsed_docblock->combined_tags['implements'] as $template_line) { |
||
900 | $info->template_implements[] = trim(preg_replace('@^[ \t]*\*@m', '', $template_line)); |
||
901 | } |
||
902 | } |
||
903 | |||
904 | if (isset($parsed_docblock->tags['psalm-yield']) |
||
905 | ) { |
||
906 | $yield = reset($parsed_docblock->tags['psalm-yield']); |
||
907 | |||
908 | $info->yield = trim(preg_replace('@^[ \t]*\*@m', '', $yield)); |
||
909 | } |
||
910 | |||
911 | if (isset($parsed_docblock->tags['deprecated'])) { |
||
912 | $info->deprecated = true; |
||
913 | } |
||
914 | |||
915 | if (isset($parsed_docblock->tags['internal'])) { |
||
916 | $info->internal = true; |
||
917 | } |
||
918 | |||
919 | if (isset($parsed_docblock->tags['final'])) { |
||
920 | $info->final = true; |
||
921 | } |
||
922 | |||
923 | if (isset($parsed_docblock->tags['psalm-internal'])) { |
||
924 | $psalm_internal = reset($parsed_docblock->tags['psalm-internal']); |
||
925 | if ($psalm_internal) { |
||
926 | $info->psalm_internal = $psalm_internal; |
||
927 | } else { |
||
928 | throw new DocblockParseException('psalm-internal annotation used without specifying namespace'); |
||
929 | } |
||
930 | |||
931 | if (! $info->internal) { |
||
932 | throw new DocblockParseException('@psalm-internal annotation used without @internal'); |
||
933 | } |
||
934 | } |
||
935 | |||
936 | if (isset($parsed_docblock->tags['mixin'])) { |
||
937 | $mixin = trim(reset($parsed_docblock->tags['mixin'])); |
||
938 | $doc_line_parts = self::splitDocLine($mixin); |
||
939 | $mixin = $doc_line_parts[0]; |
||
940 | |||
941 | if ($mixin) { |
||
942 | $info->mixin = $mixin; |
||
943 | } else { |
||
944 | throw new DocblockParseException('@mixin annotation used without specifying class'); |
||
945 | } |
||
946 | } |
||
947 | |||
948 | if (isset($parsed_docblock->tags['psalm-seal-properties'])) { |
||
949 | $info->sealed_properties = true; |
||
950 | } |
||
951 | |||
952 | if (isset($parsed_docblock->tags['psalm-seal-methods'])) { |
||
953 | $info->sealed_methods = true; |
||
954 | } |
||
955 | |||
956 | if (isset($parsed_docblock->tags['psalm-immutable']) |
||
957 | || isset($parsed_docblock->tags['psalm-mutation-free']) |
||
958 | ) { |
||
959 | $info->mutation_free = true; |
||
960 | $info->external_mutation_free = true; |
||
961 | $info->taint_specialize = true; |
||
962 | } |
||
963 | |||
964 | if (isset($parsed_docblock->tags['psalm-external-mutation-free'])) { |
||
965 | $info->external_mutation_free = true; |
||
966 | } |
||
967 | |||
968 | if (isset($parsed_docblock->tags['psalm-taint-specialize'])) { |
||
969 | $info->taint_specialize = true; |
||
970 | } |
||
971 | |||
972 | if (isset($parsed_docblock->tags['psalm-override-property-visibility'])) { |
||
973 | $info->override_property_visibility = true; |
||
974 | } |
||
975 | |||
976 | if (isset($parsed_docblock->tags['psalm-override-method-visibility'])) { |
||
977 | $info->override_method_visibility = true; |
||
978 | } |
||
979 | |||
980 | if (isset($parsed_docblock->tags['psalm-suppress'])) { |
||
981 | foreach ($parsed_docblock->tags['psalm-suppress'] as $offset => $suppress_entry) { |
||
982 | $info->suppressed_issues[$offset + $comment->getFilePos()] = preg_split('/[\s]+/', $suppress_entry)[0]; |
||
983 | } |
||
984 | } |
||
985 | |||
986 | if (isset($parsed_docblock->tags['psalm-import-type'])) { |
||
987 | foreach ($parsed_docblock->tags['psalm-import-type'] as $imported_type_entry) { |
||
988 | /** @psalm-suppress InvalidPropertyAssignmentValue */ |
||
989 | $info->imported_types[] = preg_split('/[\s]+/', $imported_type_entry); |
||
990 | } |
||
991 | } |
||
992 | |||
993 | if (isset($parsed_docblock->combined_tags['method'])) { |
||
994 | foreach ($parsed_docblock->combined_tags['method'] as $offset => $method_entry) { |
||
995 | $method_entry = preg_replace('/[ \t]+/', ' ', trim($method_entry)); |
||
996 | |||
997 | $docblock_lines = []; |
||
998 | |||
999 | $is_static = false; |
||
1000 | |||
1001 | $has_return = false; |
||
1002 | |||
1003 | if (!preg_match('/^([a-z_A-Z][a-z_0-9A-Z]+) *\(/', $method_entry, $matches)) { |
||
1004 | $doc_line_parts = self::splitDocLine($method_entry); |
||
1005 | |||
1006 | if ($doc_line_parts[0] === 'static' && !strpos($doc_line_parts[1], '(')) { |
||
1007 | $is_static = true; |
||
1008 | array_shift($doc_line_parts); |
||
1009 | } |
||
1010 | |||
1011 | if (count($doc_line_parts) > 1) { |
||
1012 | $docblock_lines[] = '@return ' . array_shift($doc_line_parts); |
||
1013 | $has_return = true; |
||
1014 | |||
1015 | $method_entry = implode(' ', $doc_line_parts); |
||
1016 | } |
||
1017 | } |
||
1018 | |||
1019 | $method_entry = trim(preg_replace('/\/\/.*/', '', $method_entry)); |
||
1020 | |||
1021 | $method_entry = preg_replace( |
||
1022 | '/array\(([0-9a-zA-Z_\'\" ]+,)*([0-9a-zA-Z_\'\" ]+)\)/', |
||
1023 | '[]', |
||
1024 | $method_entry |
||
1025 | ); |
||
1026 | |||
1027 | $end_of_method_regex = '/(?<!array\()\) ?(\: ?(\??[\\\\a-zA-Z0-9_]+))?/'; |
||
1028 | |||
1029 | if (preg_match($end_of_method_regex, $method_entry, $matches, PREG_OFFSET_CAPTURE)) { |
||
1030 | $method_entry = substr($method_entry, 0, (int) $matches[0][1] + strlen((string) $matches[0][0])); |
||
1031 | } |
||
1032 | |||
1033 | $method_entry = str_replace([', ', '( '], [',', '('], $method_entry); |
||
1034 | $method_entry = preg_replace('/ (?!(\$|\.\.\.|&))/', '', trim($method_entry)); |
||
1035 | |||
1036 | // replace array bracket contents |
||
1037 | $method_entry = preg_replace('/\[([0-9a-zA-Z_\'\" ]+,)*([0-9a-zA-Z_\'\" ]+)\]/', '[]', $method_entry); |
||
1038 | |||
1039 | if (!$method_entry) { |
||
1040 | throw new DocblockParseException('No @method entry specified'); |
||
1041 | } |
||
1042 | |||
1043 | try { |
||
1044 | $parse_tree_creator = new ParseTreeCreator( |
||
1045 | TypeTokenizer::getFullyQualifiedTokens( |
||
1046 | $method_entry, |
||
1047 | $aliases, |
||
1048 | null |
||
1049 | ) |
||
1050 | ); |
||
1051 | |||
1052 | $method_tree = $parse_tree_creator->create(); |
||
1053 | } catch (TypeParseTreeException $e) { |
||
1054 | throw new DocblockParseException($method_entry . ' is not a valid method'); |
||
1055 | } |
||
1056 | |||
1057 | if (!$method_tree instanceof ParseTree\MethodWithReturnTypeTree |
||
1058 | && !$method_tree instanceof ParseTree\MethodTree) { |
||
1059 | throw new DocblockParseException($method_entry . ' is not a valid method'); |
||
1060 | } |
||
1061 | |||
1062 | if ($method_tree instanceof ParseTree\MethodWithReturnTypeTree) { |
||
1063 | if (!$has_return) { |
||
1064 | $docblock_lines[] = '@return ' . TypeParser::getTypeFromTree( |
||
1065 | $method_tree->children[1], |
||
1066 | $codebase |
||
1067 | )->toNamespacedString($aliases->namespace, $aliases->uses, null, false); |
||
1068 | } |
||
1069 | |||
1070 | $method_tree = $method_tree->children[0]; |
||
1071 | } |
||
1072 | |||
1073 | if (!$method_tree instanceof ParseTree\MethodTree) { |
||
1074 | throw new DocblockParseException($method_entry . ' is not a valid method'); |
||
1075 | } |
||
1076 | |||
1077 | $args = []; |
||
1078 | |||
1079 | foreach ($method_tree->children as $method_tree_child) { |
||
1080 | if (!$method_tree_child instanceof ParseTree\MethodParamTree) { |
||
1081 | throw new DocblockParseException($method_entry . ' is not a valid method'); |
||
1082 | } |
||
1083 | |||
1084 | $args[] = ($method_tree_child->byref ? '&' : '') |
||
1085 | . ($method_tree_child->variadic ? '...' : '') |
||
1086 | . $method_tree_child->name |
||
1087 | . ($method_tree_child->default != '' ? ' = ' . $method_tree_child->default : ''); |
||
1088 | |||
1089 | |||
1090 | if ($method_tree_child->children) { |
||
1091 | try { |
||
1092 | $param_type = TypeParser::getTypeFromTree($method_tree_child->children[0], $codebase); |
||
1093 | } catch (\Exception $e) { |
||
1094 | throw new DocblockParseException( |
||
1095 | 'Badly-formatted @method string ' . $method_entry . ' - ' . $e |
||
1096 | ); |
||
1097 | } |
||
1098 | $docblock_lines[] = '@param \\' . $param_type . ' ' |
||
1099 | . ($method_tree_child->variadic ? '...' : '') |
||
1100 | . $method_tree_child->name; |
||
1101 | } |
||
1102 | } |
||
1103 | |||
1104 | $function_string = 'function ' . $method_tree->value . '(' . implode(', ', $args) . ')'; |
||
1105 | |||
1106 | if ($is_static) { |
||
1107 | $function_string = 'static ' . $function_string; |
||
1108 | } |
||
1109 | |||
1110 | $function_docblock = $docblock_lines ? "/**\n * " . implode("\n * ", $docblock_lines) . "\n*/\n" : ""; |
||
1111 | |||
1112 | $php_string = '<?php class A { ' . $function_docblock . ' public ' . $function_string . '{} }'; |
||
1113 | |||
1114 | try { |
||
1115 | $statements = \Psalm\Internal\Provider\StatementsProvider::parseStatements($php_string); |
||
1116 | } catch (\Exception $e) { |
||
1117 | throw new DocblockParseException('Badly-formatted @method string ' . $method_entry); |
||
1118 | } |
||
1119 | |||
1120 | if (!$statements |
||
1121 | || !$statements[0] instanceof \PhpParser\Node\Stmt\Class_ |
||
1122 | || !isset($statements[0]->stmts[0]) |
||
1123 | || !$statements[0]->stmts[0] instanceof \PhpParser\Node\Stmt\ClassMethod |
||
1124 | ) { |
||
1125 | throw new DocblockParseException('Badly-formatted @method string ' . $method_entry); |
||
1126 | } |
||
1127 | |||
1128 | /** @var \PhpParser\Comment\Doc */ |
||
1129 | $node_doc_comment = $node->getDocComment(); |
||
1130 | |||
1131 | $statements[0]->stmts[0]->setAttribute('startLine', $node_doc_comment->getLine()); |
||
1132 | $statements[0]->stmts[0]->setAttribute('startFilePos', $node_doc_comment->getFilePos()); |
||
1133 | $statements[0]->stmts[0]->setAttribute('endFilePos', $node->getAttribute('startFilePos')); |
||
1134 | |||
1135 | if ($doc_comment = $statements[0]->stmts[0]->getDocComment()) { |
||
1136 | $statements[0]->stmts[0]->setDocComment( |
||
1137 | new \PhpParser\Comment\Doc( |
||
1138 | $doc_comment->getText(), |
||
1139 | $comment->getLine() + substr_count($comment->getText(), "\n", 0, $offset), |
||
1140 | $node_doc_comment->getFilePos() |
||
1141 | ) |
||
1142 | ); |
||
1143 | } |
||
1144 | |||
1145 | $info->methods[] = $statements[0]->stmts[0]; |
||
1146 | } |
||
1147 | } |
||
1148 | |||
1149 | self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'property'); |
||
1150 | self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'psalm-property'); |
||
1151 | self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'property-read'); |
||
1152 | self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'psalm-property-read'); |
||
1153 | self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'property-write'); |
||
1154 | self::addMagicPropertyToInfo($comment, $info, $parsed_docblock->tags, 'psalm-property-write'); |
||
1155 | |||
1156 | return $info; |
||
1157 | } |
||
1158 | |||
1159 | /** |
||
1160 | * @param ClassLikeDocblockComment $info |
||
1161 | * @param array<string, array<int, string>> $specials |
||
1162 | * @param 'property'|'psalm-property'|'property-read'| |
||
1163 | * 'psalm-property-read'|'property-write'|'psalm-property-write' $property_tag |
||
1164 | * |
||
1165 | * @throws DocblockParseException |
||
1166 | * |
||
1167 | * @return void |
||
1168 | */ |
||
1169 | protected static function addMagicPropertyToInfo( |
||
1224 | |||
1225 | /** |
||
1226 | * @param string $return_block |
||
1227 | * |
||
1228 | * @throws DocblockParseException if an invalid string is found |
||
1229 | * |
||
1230 | * @return array<string> |
||
1231 | */ |
||
1232 | public static function splitDocLine($return_block) |
||
1355 | |||
1356 | /** |
||
1357 | * @param ParsedDocblock $parsed_docblock |
||
1358 | * |
||
1359 | * @return void |
||
1360 | * |
||
1361 | * @throws DocblockParseException if a duplicate is found |
||
1362 | */ |
||
1363 | private static function checkDuplicatedTags(ParsedDocblock $parsed_docblock) |
||
1364 | { |
||
1376 | |||
1377 | /** |
||
1378 | * @param array<int, string> $param |
||
1379 | * |
||
1380 | * @return void |
||
1381 | * |
||
1382 | * @throws DocblockParseException if a duplicate is found |
||
1383 | */ |
||
1384 | private static function checkDuplicatedParams(array $param) |
||
1392 | |||
1393 | /** |
||
1394 | * @param array<int, string> $lines |
||
1395 | * |
||
1396 | * @return list<string> |
||
1397 | */ |
||
1398 | private static function extractAllParamNames(array $lines) |
||
1412 | } |
||
1413 |
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.