Repository navigation
Expand file tree
/
Copy pathCleanupNodeVisitor.php
More file actions
146 lines (125 loc) · 5.4 KB
/
Copy pathCleanupNodeVisitor.php
File metadata and controls
146 lines (125 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
declare(strict_types=1);
namespace Efabrica\PHPStanLatte\Compiler\NodeVisitor;
use Efabrica\PHPStanLatte\Resolver\NameResolver\NameResolver;
use InvalidArgumentException;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\Throw_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use function array_shift;
use function count;
use function implode;
use function in_array;
final class CleanupNodeVisitor extends NodeVisitorAbstract
{
private NameResolver $nameResolver;
public function __construct(NameResolver $nameResolver)
{
$this->nameResolver = $nameResolver;
}
public function leaveNode(Node $node)
{
if ($node instanceof Expression) {
// if only one expr in Expression is Variable, we can remove it
if ($node->expr instanceof Variable) {
return NodeTraverser::REMOVE_NODE;
}
}
if ($node instanceof TryCatch) {
// replace function call for closure with closure stmts
$stmts = $node->stmts;
if (count($stmts) === 1 && $stmts[0] instanceof Expression) {
$expression = $stmts[0];
if (!$expression->expr instanceof FuncCall) {
return null;
}
if (!$expression->expr->name instanceof Closure) {
return null;
}
if (count($expression->expr->getArgs()) !== 1) {
return null;
}
if ($this->nameResolver->resolve($expression->expr->getArgs()[0]->value) !== 'get_defined_vars') {
return null;
}
/** @var Closure $closure */
$closure = $expression->expr->name;
$node->stmts = $closure->stmts;
return $node;
}
}
if ($node instanceof If_) {
// replace if (false) to next condition
if ($node->cond instanceof ConstFetch && $this->nameResolver->resolve($node->cond->name) === 'false') {
$elseIfs = $node->elseifs;
$else = $node->else;
$firstElseIf = array_shift($elseIfs);
if ($firstElseIf === null) {
if (!$else instanceof Else_) {
// no elseif nor else
return NodeTraverser::REMOVE_NODE;
}
// only else
return $else->stmts;
}
$subnodes = [
'stmts' => $firstElseIf->stmts,
'elseifs' => $elseIfs,
'else' => $else,
];
return new If_($firstElseIf->cond, $subnodes, $node->getAttributes());
}
// Prevent unwanted errors on dynamic components
if (($node->cond instanceof FuncCall && $this->nameResolver->resolve($node->cond->name) === 'is_object') ||
$node->cond instanceof BooleanNot && $node->cond->expr instanceof FuncCall && $this->nameResolver->resolve($node->cond->expr->name) === 'is_object'
) {
$firstStmt = $node->stmts[0] ?? null;
if ($firstStmt instanceof Expression &&
$firstStmt->expr instanceof Assign &&
$firstStmt->expr->var instanceof Variable &&
in_array($varName = $this->nameResolver->resolve($firstStmt->expr->var), ['_tmp', 'ʟ_tmp'], true)
) {
$docComment = $node->getDocComment();
$docComments = $docComment ? [$docComment->getText()] : [];
$docComments[] = '/** @phpstan-ignore-next-line */';
$node->setDocComment(new Doc(implode("\n", $docComments)));
$nodes = [];
if ($node->cond instanceof BooleanNot && $node->cond->expr instanceof FuncCall && isset($node->cond->expr->getArgs()[0])) { // latte 3
$nodes[] = new Expression($node->cond->expr->getArgs()[0]->value);
$node->cond->expr->args[0] = new Arg(new Variable($varName));
}
$elseIgnore = new Nop();
$elseIgnore->setDocComment(new Doc('/** @phpstan-ignore-next-line */'));
$node->stmts[] = $elseIgnore;
$nodes[] = $node;
$guardNode = new If_(new Identical(new Variable($varName), new ConstFetch(new Name('null'))), [
'stmts' => [
new Expression(new Throw_(new New_(new Name(InvalidArgumentException::class)))),
],
]);
$guardNode->setDocComment(new Doc('/** @phpstan-ignore-next-line */'));
$nodes[] = $guardNode;
return $nodes;
}
}
}
return null;
}
}