Skip to content

Commit e6ec1f4

Browse files
authored
Use visitor to add @phpstan-return void (#121)
1 parent 547df04 commit e6ec1f4

File tree

2 files changed

+538
-62
lines changed

2 files changed

+538
-62
lines changed

visitor.php

Lines changed: 89 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
1010
use phpDocumentor\Reflection\Type;
1111
use phpDocumentor\Reflection\Types\Never_;
12+
use phpDocumentor\Reflection\Types\Void_;
1213
use PhpParser\Comment\Doc;
1314
use PhpParser\Node;
1415
use PhpParser\NodeFinder;
@@ -18,7 +19,6 @@
1819
use PhpParser\Node\Expr\ArrayItem;
1920
use PhpParser\Node\Expr\Exit_;
2021
use PhpParser\Node\Expr\FuncCall;
21-
use PhpParser\Node\Expr\Variable;
2222
use PhpParser\Node\Scalar\String_;
2323
use PhpParser\Node\Stmt\Class_;
2424
use PhpParser\Node\Stmt\ClassMethod;
@@ -282,14 +282,18 @@ public function format(int $level = 1): array
282282
*/
283283
private $additionalTagStrings = [];
284284

285+
/** @var \PhpParser\NodeFinder */
286+
private $nodeFinder;
287+
285288
public function __construct()
286289
{
287290
$this->docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
291+
$this->nodeFinder = new NodeFinder();
288292
}
289293

290294
public function enterNode(Node $node)
291295
{
292-
$neverReturn = self::isNeverReturn($node);
296+
$voidOrNever = $this->voidOrNever($node);
293297

294298
parent::enterNode($node);
295299

@@ -317,28 +321,31 @@ public function enterNode(Node $node)
317321
);
318322
}
319323
}
320-
321-
$additions = $this->generateAdditionalTagsFromDoc($docComment);
322324
$node->setAttribute('fullSymbolName', $symbolName);
323325

326+
$additions = $this->generateAdditionalTagsFromDoc($docComment);
324327
if (count($additions) > 0) {
325328
$this->additionalTags[ $symbolName ] = $additions;
326329
}
327330

328331
$additions = $this->getAdditionalTagsFromMap($symbolName);
329-
330332
if (count($additions) > 0) {
331333
$this->additionalTagStrings[ $symbolName ] = $additions;
332334
}
333335

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+
}
342349
}
343350

344351
return null;
@@ -1000,66 +1007,87 @@ private static function isOptional(string $description): bool
10001007
return (stripos($description, 'Optional') !== false)
10011008
|| (stripos($description, 'Default ') !== false)
10021009
|| (stripos($description, 'Default: ') !== false)
1003-
|| (stripos($description, 'Defaults to ') !== false)
1004-
;
1010+
|| (stripos($description, 'Defaults to ') !== false);
10051011
}
10061012

1007-
private static function isNeverReturn(Node $node): bool
1013+
private function voidOrNever(Node $node): string
10081014
{
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 '';
10141017
}
10151018

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 '';
10381022
}
10391023

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);
10441025

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';
10491043
}
10501044

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';
10601087
}
1088+
return '';
10611089
}
10621090
}
1063-
return true;
1091+
return '';
10641092
}
10651093
};

0 commit comments

Comments
 (0)