|
9 | 9 | use phpDocumentor\Reflection\DocBlock\Tags\Var_; |
10 | 10 | use phpDocumentor\Reflection\Type; |
11 | 11 | use phpDocumentor\Reflection\Types\Never_; |
| 12 | +use phpDocumentor\Reflection\Types\Void_; |
12 | 13 | use PhpParser\Comment\Doc; |
13 | 14 | use PhpParser\Node; |
14 | 15 | use PhpParser\NodeFinder; |
|
18 | 19 | use PhpParser\Node\Expr\ArrayItem; |
19 | 20 | use PhpParser\Node\Expr\Exit_; |
20 | 21 | use PhpParser\Node\Expr\FuncCall; |
21 | | -use PhpParser\Node\Expr\Variable; |
22 | 22 | use PhpParser\Node\Scalar\String_; |
23 | 23 | use PhpParser\Node\Stmt\Class_; |
24 | 24 | use PhpParser\Node\Stmt\ClassMethod; |
@@ -282,14 +282,18 @@ public function format(int $level = 1): array |
282 | 282 | */ |
283 | 283 | private $additionalTagStrings = []; |
284 | 284 |
|
| 285 | + /** @var \PhpParser\NodeFinder */ |
| 286 | + private $nodeFinder; |
| 287 | + |
285 | 288 | public function __construct() |
286 | 289 | { |
287 | 290 | $this->docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance(); |
| 291 | + $this->nodeFinder = new NodeFinder(); |
288 | 292 | } |
289 | 293 |
|
290 | 294 | public function enterNode(Node $node) |
291 | 295 | { |
292 | | - $neverReturn = self::isNeverReturn($node); |
| 296 | + $voidOrNever = $this->voidOrNever($node); |
293 | 297 |
|
294 | 298 | parent::enterNode($node); |
295 | 299 |
|
@@ -317,28 +321,31 @@ public function enterNode(Node $node) |
317 | 321 | ); |
318 | 322 | } |
319 | 323 | } |
320 | | - |
321 | | - $additions = $this->generateAdditionalTagsFromDoc($docComment); |
322 | 324 | $node->setAttribute('fullSymbolName', $symbolName); |
323 | 325 |
|
| 326 | + $additions = $this->generateAdditionalTagsFromDoc($docComment); |
324 | 327 | if (count($additions) > 0) { |
325 | 328 | $this->additionalTags[ $symbolName ] = $additions; |
326 | 329 | } |
327 | 330 |
|
328 | 331 | $additions = $this->getAdditionalTagsFromMap($symbolName); |
329 | | - |
330 | 332 | if (count($additions) > 0) { |
331 | 333 | $this->additionalTagStrings[ $symbolName ] = $additions; |
332 | 334 | } |
333 | 335 |
|
334 | | - if ($neverReturn) { |
335 | | - $never = new Never_(); |
336 | | - $this->additionalTagStrings[ $symbolName ] = [ |
337 | | - sprintf( |
338 | | - '@phpstan-return %s', |
339 | | - $never->__toString() |
340 | | - ) |
341 | | - ]; |
| 336 | + if ($voidOrNever !== '') { |
| 337 | + $addition = sprintf( |
| 338 | + '@phpstan-return %s', |
| 339 | + $voidOrNever === 'never' |
| 340 | + ? (new Never_())->__toString() |
| 341 | + : (new Void_())->__toString() |
| 342 | + ); |
| 343 | + if ( |
| 344 | + !isset($this->additionalTagStrings[$symbolName]) |
| 345 | + || !in_array($addition, $this->additionalTagStrings[$symbolName], true) |
| 346 | + ) { |
| 347 | + $this->additionalTagStrings[$symbolName][] = $addition; |
| 348 | + } |
342 | 349 | } |
343 | 350 |
|
344 | 351 | return null; |
@@ -1000,66 +1007,87 @@ private static function isOptional(string $description): bool |
1000 | 1007 | return (stripos($description, 'Optional') !== false) |
1001 | 1008 | || (stripos($description, 'Default ') !== false) |
1002 | 1009 | || (stripos($description, 'Default: ') !== false) |
1003 | | - || (stripos($description, 'Defaults to ') !== false) |
1004 | | - ; |
| 1010 | + || (stripos($description, 'Defaults to ') !== false); |
1005 | 1011 | } |
1006 | 1012 |
|
1007 | | - private static function isNeverReturn(Node $node): bool |
| 1013 | + private function voidOrNever(Node $node): string |
1008 | 1014 | { |
1009 | | - if (! $node instanceof Function_ && ! $node instanceof ClassMethod) { |
1010 | | - return false; |
1011 | | - } |
1012 | | - if (empty($node->stmts) ) { |
1013 | | - return false; |
| 1015 | + if (!($node instanceof Function_) && !($node instanceof ClassMethod)) { |
| 1016 | + return ''; |
1014 | 1017 | } |
1015 | 1018 |
|
1016 | | - $nodeFinder = new NodeFinder(); |
1017 | | - if ($nodeFinder->findFirstInstanceOf($node, Stmt_Return::class) instanceof Stmt_Return) { |
1018 | | - // If there is a return statement, it's not return type never. |
1019 | | - return false; |
1020 | | - }; |
1021 | | - |
1022 | | - $lastStmt = end($node->stmts); |
1023 | | - if (! $lastStmt instanceof Expression) { |
1024 | | - return false; |
1025 | | - } |
1026 | | - // If the last statement is exit, it's return type never. |
1027 | | - if ($lastStmt->expr instanceof Exit_) { |
1028 | | - return true; |
1029 | | - } |
1030 | | - if (! $lastStmt->expr instanceof FuncCall || ! $lastStmt->expr->name instanceof Name) { |
1031 | | - return false; |
1032 | | - } |
1033 | | - |
1034 | | - // If the last statement is a call to wp_send_json(_success/error), |
1035 | | - // it's return type never. |
1036 | | - if (strpos($lastStmt->expr->name->toString(), 'wp_send_json') === 0) { |
1037 | | - return true; |
| 1019 | + if (!isset($node->stmts) || count($node->stmts) === 0) { |
| 1020 | + // Interfaces and abstract methods. |
| 1021 | + return ''; |
1038 | 1022 | } |
1039 | 1023 |
|
1040 | | - // Skip all functions but wp_die(). |
1041 | | - if (strpos($lastStmt->expr->name->toString(), 'wp_die') !== 0) { |
1042 | | - return false; |
1043 | | - } |
| 1024 | + $return = $this->nodeFinder->findInstanceOf($node, Stmt_Return::class); |
1044 | 1025 |
|
1045 | | - // If wp_die is called without 3rd parameter, it's return type never. |
1046 | | - $args = $lastStmt->expr->getArgs(); |
1047 | | - if (count($args) < 3) { |
1048 | | - return true; |
| 1026 | + // If there is a return statement, it's not return type never. |
| 1027 | + if (count($return) !== 0) { |
| 1028 | + // If there is at least one return statement that is not void, |
| 1029 | + // it's not return type void. |
| 1030 | + if ( |
| 1031 | + $this->nodeFinder->findFirst( |
| 1032 | + $return, |
| 1033 | + static function (Node $node): bool { |
| 1034 | + return isset($node->expr); |
| 1035 | + } |
| 1036 | + ) !== null |
| 1037 | + ) { |
| 1038 | + return ''; |
| 1039 | + } |
| 1040 | + // If there is no return statement that is not void, |
| 1041 | + // it's return type void. |
| 1042 | + return 'void'; |
1049 | 1043 | } |
1050 | 1044 |
|
1051 | | - // If wp_die is called with 3rd parameter, we need additional checks. |
1052 | | - $argValue = $args[2]->value; |
1053 | | - if ($argValue instanceof Variable) { |
1054 | | - return false; |
1055 | | - } |
1056 | | - if ($argValue instanceof Array_) { |
1057 | | - foreach ($argValue->items as $item ) { |
1058 | | - if ($item instanceof ArrayItem && $item->key instanceof String_ && $item->key->value === 'exit') { |
1059 | | - return false; |
| 1045 | + // Check for never return type. |
| 1046 | + foreach ($node->stmts as $stmt) { |
| 1047 | + if (!($stmt instanceof Expression)) { |
| 1048 | + continue; |
| 1049 | + } |
| 1050 | + // If a first level statement is exit/die, it's return type never. |
| 1051 | + if ($stmt->expr instanceof Exit_) { |
| 1052 | + return 'never'; |
| 1053 | + } |
| 1054 | + if (!($stmt->expr instanceof FuncCall) || !($stmt->expr->name instanceof Name)) { |
| 1055 | + continue; |
| 1056 | + } |
| 1057 | + $name = $stmt->expr->name; |
| 1058 | + // If a first level statement is a call to wp_send_json(_success/error), |
| 1059 | + // it's return type never. |
| 1060 | + if (strpos($name->toString(), 'wp_send_json') === 0) { |
| 1061 | + return 'never'; |
| 1062 | + } |
| 1063 | + // Skip all functions but wp_die(). |
| 1064 | + if (strpos($name->toString(), 'wp_die') !== 0) { |
| 1065 | + continue; |
| 1066 | + } |
| 1067 | + $args = $stmt->expr->getArgs(); |
| 1068 | + // If wp_die is called without 3rd parameter, it's return type never. |
| 1069 | + if (count($args) < 3) { |
| 1070 | + return 'never'; |
| 1071 | + } |
| 1072 | + // If wp_die is called with 3rd parameter, we need additional checks. |
| 1073 | + $argValue = $args[2]->value; |
| 1074 | + if (!($argValue instanceof Array_)) { |
| 1075 | + continue; |
| 1076 | + } |
| 1077 | + foreach ($argValue->items as $item) { |
| 1078 | + if (!($item instanceof ArrayItem && $item->key instanceof String_ && $item->key->value === 'exit')) { |
| 1079 | + continue; |
| 1080 | + } |
| 1081 | + if ( |
| 1082 | + ($item->value instanceof Node\Expr\ConstFetch && strtolower($item->value->name->toString()) === 'true') |
| 1083 | + || ($item->value instanceof Node\Scalar\LNumber && $item->value->value === 1) |
| 1084 | + || ($item->value instanceof Node\Scalar\String_ && $item->value->value !== '' && $item->value->value !== '0') |
| 1085 | + ) { |
| 1086 | + return 'never'; |
1060 | 1087 | } |
| 1088 | + return ''; |
1061 | 1089 | } |
1062 | 1090 | } |
1063 | | - return true; |
| 1091 | + return ''; |
1064 | 1092 | } |
1065 | 1093 | }; |
0 commit comments