-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathThrowIfRector.php
117 lines (100 loc) · 3.33 KB
/
ThrowIfRector.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
<?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\Throw_;
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_\ThrowIfRector\ThrowIfRectorTest
*/
class ThrowIfRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change if throw to throw_if', [
new CodeSample(
<<<'CODE_SAMPLE'
if ($condition) {
throw new Exception();
}
if (!$condition) {
throw new Exception();
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
throw_if($condition, new Exception());
throw_unless($condition, new Exception());
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 throw statement inside the if
if (count($ifStmts) !== 1 || ! $ifStmts[0] instanceof Expression || ! $ifStmts[0]->expr instanceof Throw_) {
return null;
}
$condition = $node->cond;
$throwExpr = $ifStmts[0]->expr;
if ($this->exceptionUsesVariablesAssignedByCondition($throwExpr, $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('throw_unless'), [
new Arg($condition->expr),
new Arg($throwExpr->expr),
]));
} else {
// Create a new throw_if function call
return new Expression(new FuncCall(new Name('throw_if'), [
new Arg($condition),
new Arg($throwExpr->expr),
]));
}
}
/**
* 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;
}
}