|
8 | 8 | use phpDocumentor\Reflection\DocBlock\Tags\Return_; |
9 | 9 | use phpDocumentor\Reflection\DocBlock\Tags\Var_; |
10 | 10 | use phpDocumentor\Reflection\Type; |
| 11 | +use phpDocumentor\Reflection\Types\Never_; |
11 | 12 | use PhpParser\Comment\Doc; |
12 | 13 | use PhpParser\Node; |
| 14 | +use PhpParser\NodeFinder; |
13 | 15 | use PhpParser\Node\Identifier; |
| 16 | +use PhpParser\Node\Name; |
| 17 | +use PhpParser\Node\Expr\Array_; |
| 18 | +use PhpParser\Node\Expr\ArrayItem; |
| 19 | +use PhpParser\Node\Expr\Exit_; |
| 20 | +use PhpParser\Node\Expr\FuncCall; |
| 21 | +use PhpParser\Node\Expr\Variable; |
| 22 | +use PhpParser\Node\Scalar\String_; |
14 | 23 | use PhpParser\Node\Stmt\Class_; |
15 | 24 | use PhpParser\Node\Stmt\ClassMethod; |
| 25 | +use PhpParser\Node\Stmt\Expression; |
16 | 26 | use PhpParser\Node\Stmt\Function_; |
17 | 27 | use PhpParser\Node\Stmt\Property; |
| 28 | +use PhpParser\Node\Stmt\Return_ as Stmt_Return; |
18 | 29 | use StubsGenerator\NodeVisitor; |
19 | 30 |
|
20 | 31 | abstract class WithChildren |
@@ -262,6 +273,9 @@ public function __construct() |
262 | 273 |
|
263 | 274 | public function enterNode(Node $node) |
264 | 275 | { |
| 276 | + |
| 277 | + $neverReturn = self::isNeverReturn($node); |
| 278 | + |
265 | 279 | parent::enterNode($node); |
266 | 280 |
|
267 | 281 | if (!($node instanceof Function_) && !($node instanceof ClassMethod) && !($node instanceof Property) && !($node instanceof Class_)) { |
@@ -302,6 +316,16 @@ public function enterNode(Node $node) |
302 | 316 | $this->additionalTagStrings[ $symbolName ] = $additions; |
303 | 317 | } |
304 | 318 |
|
| 319 | + if ($neverReturn) { |
| 320 | + $never = new Never_(); |
| 321 | + $this->additionalTagStrings[ $symbolName ] = [ |
| 322 | + sprintf( |
| 323 | + '@phpstan-return %s', |
| 324 | + $never->__toString() |
| 325 | + ) |
| 326 | + ]; |
| 327 | + } |
| 328 | + |
305 | 329 | return null; |
306 | 330 | } |
307 | 331 |
|
@@ -895,4 +919,63 @@ private static function isOptional(string $description): bool |
895 | 919 | || (stripos($description, 'Defaults to ') !== false) |
896 | 920 | ; |
897 | 921 | } |
| 922 | + |
| 923 | + private static function isNeverReturn(Node $node): bool |
| 924 | + { |
| 925 | + if (! $node instanceof Function_ && ! $node instanceof ClassMethod) { |
| 926 | + return false; |
| 927 | + } |
| 928 | + if (empty($node->stmts) ) { |
| 929 | + return false; |
| 930 | + } |
| 931 | + |
| 932 | + $nodeFinder = new NodeFinder(); |
| 933 | + if ($nodeFinder->findFirstInstanceOf($node, Stmt_Return::class) instanceof Stmt_Return) { |
| 934 | + // If there is a return statement, it's not return type never. |
| 935 | + return false; |
| 936 | + }; |
| 937 | + |
| 938 | + $lastStmt = end($node->stmts); |
| 939 | + if (! $lastStmt instanceof Expression) { |
| 940 | + return false; |
| 941 | + } |
| 942 | + // If the last statement is exit, it's return type never. |
| 943 | + if ($lastStmt->expr instanceof Exit_) { |
| 944 | + return true; |
| 945 | + } |
| 946 | + if (! $lastStmt->expr instanceof FuncCall || ! $lastStmt->expr->name instanceof Name) { |
| 947 | + return false; |
| 948 | + } |
| 949 | + |
| 950 | + // If the last statement is a call to wp_send_json(_success/error), |
| 951 | + // it's return type never. |
| 952 | + if (strpos($lastStmt->expr->name->toString(), 'wp_send_json') === 0) { |
| 953 | + return true; |
| 954 | + } |
| 955 | + |
| 956 | + // Skip all functions but wp_die(). |
| 957 | + if (strpos($lastStmt->expr->name->toString(), 'wp_die') !== 0) { |
| 958 | + return false; |
| 959 | + } |
| 960 | + |
| 961 | + // If wp_die is called without 3rd parameter, it's return type never. |
| 962 | + $args = $lastStmt->expr->getArgs(); |
| 963 | + if (count($args) < 3) { |
| 964 | + return true; |
| 965 | + } |
| 966 | + |
| 967 | + // If wp_die is called with 3rd parameter, we need additional checks. |
| 968 | + $argValue = $args[2]->value; |
| 969 | + if ($argValue instanceof Variable) { |
| 970 | + return false; |
| 971 | + } |
| 972 | + if ($argValue instanceof Array_) { |
| 973 | + foreach ($argValue->items as $item ) { |
| 974 | + if ($item instanceof ArrayItem && $item->key instanceof String_ && $item->key->value === 'exit') { |
| 975 | + return false; |
| 976 | + } |
| 977 | + } |
| 978 | + } |
| 979 | + return true; |
| 980 | + } |
898 | 981 | }; |
0 commit comments