-
Notifications
You must be signed in to change notification settings - Fork 63
/
Copy pathAbortIfRector.php
123 lines (106 loc) · 3.53 KB
/
AbortIfRector.php
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
<?php
namespace RectorLaravel\Rector\If_;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\If_;
use PhpParser\NodeVisitor;
use RectorLaravel\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \RectorLaravel\Tests\Rector\If_\AbortIfRector\AbortIfRectorTest
*/
class AbortIfRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change if abort to abort_if', [
new CodeSample(
<<<'CODE_SAMPLE'
if ($condition) {
abort(404);
}
if (!$condition) {
abort(404);
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
abort_if($condition, 404);
abort_unless($condition, 404);
CODE_SAMPLE
),
]);
}
public function getNodeTypes(): array
{
return [If_::class];
}
public function refactor(Node $node): ?Node
{
if (! $node instanceof If_) {
return null;
}
$ifStmts = $node->stmts;
// Check if there's a single statement inside the if block to call abort()
if (
count($ifStmts) === 1 &&
$ifStmts[0] instanceof Expression &&
$ifStmts[0]->expr instanceof FuncCall &&
$ifStmts[0]->expr->name instanceof Name &&
$this->isName($ifStmts[0]->expr, 'abort')
) {
$condition = $node->cond;
/** @var FuncCall $abortCall */
$abortCall = $ifStmts[0]->expr;
if ($this->exceptionUsesVariablesAssignedByCondition($abortCall, $condition)) {
return null;
}
// Check if the condition is a negation
if ($condition instanceof BooleanNot) {
// Create a new throw_unless function call
return new Expression(new FuncCall(new Name('abort_unless'), [
new Arg($condition->expr),
...$abortCall->args,
]));
} else {
// Create a new throw_if function call
return new Expression(new FuncCall(new Name('abort_if'), [
new Arg($condition),
...$abortCall->args,
]));
}
}
return null;
}
/**
* Make sure the exception doesn't use variables assigned by the condition or this
* will cause broken code to be generated
*/
private function exceptionUsesVariablesAssignedByCondition(Expr $throwExpr, Expr $condition): bool
{
$conditionVariables = [];
$returnValue = false;
$this->traverseNodesWithCallable($condition, function (Node $node) use (&$conditionVariables): null {
if ($node instanceof Assign) {
$conditionVariables[] = $this->getName($node->var);
}
return null;
});
$this->traverseNodesWithCallable($throwExpr, function (Node $node) use ($conditionVariables, &$returnValue): ?int {
if ($node instanceof Variable && in_array($this->getName($node), $conditionVariables, true)) {
$returnValue = true;
return NodeVisitor::STOP_TRAVERSAL;
}
return null;
});
return $returnValue;
}
}