Skip to content

Commit 4582162

Browse files
authored
Add @phpstan-return never (#110)
1 parent f22b00c commit 4582162

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

visitor.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@
88
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
99
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
1010
use phpDocumentor\Reflection\Type;
11+
use phpDocumentor\Reflection\Types\Never_;
1112
use PhpParser\Comment\Doc;
1213
use PhpParser\Node;
14+
use PhpParser\NodeFinder;
1315
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_;
1423
use PhpParser\Node\Stmt\Class_;
1524
use PhpParser\Node\Stmt\ClassMethod;
25+
use PhpParser\Node\Stmt\Expression;
1626
use PhpParser\Node\Stmt\Function_;
1727
use PhpParser\Node\Stmt\Property;
28+
use PhpParser\Node\Stmt\Return_ as Stmt_Return;
1829
use StubsGenerator\NodeVisitor;
1930

2031
abstract class WithChildren
@@ -262,6 +273,9 @@ public function __construct()
262273

263274
public function enterNode(Node $node)
264275
{
276+
277+
$neverReturn = self::isNeverReturn($node);
278+
265279
parent::enterNode($node);
266280

267281
if (!($node instanceof Function_) && !($node instanceof ClassMethod) && !($node instanceof Property) && !($node instanceof Class_)) {
@@ -302,6 +316,16 @@ public function enterNode(Node $node)
302316
$this->additionalTagStrings[ $symbolName ] = $additions;
303317
}
304318

319+
if ($neverReturn) {
320+
$never = new Never_();
321+
$this->additionalTagStrings[ $symbolName ] = [
322+
sprintf(
323+
'@phpstan-return %s',
324+
$never->__toString()
325+
)
326+
];
327+
}
328+
305329
return null;
306330
}
307331

@@ -895,4 +919,63 @@ private static function isOptional(string $description): bool
895919
|| (stripos($description, 'Defaults to ') !== false)
896920
;
897921
}
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+
}
898981
};

0 commit comments

Comments
 (0)