Skip to content

Commit 2a6b0a8

Browse files
committed
feat: improve AppEnvironmentComparisonToParameterRector
This adds several improvements: - support for the `in_array` function - support for != and !== operators - support for the cleaner `isLocal` and `isProduction` methods
1 parent 75c549e commit 2a6b0a8

File tree

4 files changed

+192
-43
lines changed

4 files changed

+192
-43
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"symplify/rule-doc-generator-contracts": "^11.2"
1111
},
1212
"require-dev": {
13-
"nikic/php-parser": "^5.3",
13+
"nikic/php-parser": "^5.6",
1414
"phpstan/extension-installer": "^1.3",
1515
"phpstan/phpstan": "^2.0",
1616
"phpstan/phpstan-deprecation-rules": "^2.0",

src/Rector/Expr/AppEnvironmentComparisonToParameterRector.php

Lines changed: 129 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@
77
use PhpParser\Node;
88
use PhpParser\Node\Arg;
99
use PhpParser\Node\Expr;
10+
use PhpParser\Node\Expr\BinaryOp;
1011
use PhpParser\Node\Expr\BinaryOp\Equal;
1112
use PhpParser\Node\Expr\BinaryOp\Identical;
13+
use PhpParser\Node\Expr\BinaryOp\NotEqual;
14+
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
15+
use PhpParser\Node\Expr\BooleanNot;
16+
use PhpParser\Node\Expr\CallLike;
17+
use PhpParser\Node\Expr\FuncCall;
1218
use PhpParser\Node\Expr\MethodCall;
1319
use PhpParser\Node\Expr\StaticCall;
20+
use PhpParser\Node\Identifier;
1421
use PhpParser\Node\Scalar\String_;
1522
use PHPStan\Type\ObjectType;
1623
use RectorLaravel\AbstractRector;
@@ -25,82 +32,169 @@ class AppEnvironmentComparisonToParameterRector extends AbstractRector
2532
public function getRuleDefinition(): RuleDefinition
2633
{
2734
return new RuleDefinition(
28-
'Replace `$app->environment() === \'local\'` with `$app->environment(\'local\')`',
35+
'Replace app environment comparison with parameter or method call',
2936
[
3037
new CodeSample(
3138
<<<'CODE_SAMPLE'
32-
$app->environment() === 'production';
39+
$app->environment() === 'local';
40+
$app->environment() !== 'production';
41+
$app->environment() === 'testing';
42+
in_array($app->environment(), ['local', 'testing']);
3343
CODE_SAMPLE
3444
,
3545
<<<'CODE_SAMPLE'
36-
$app->environment('production');
46+
$app->isLocal();
47+
! $app->isProduction();
48+
$app->environment('testing');
49+
$app->environment(['local', 'testing']);
3750
CODE_SAMPLE
3851
),
3952
]
4053
);
4154
}
4255

56+
/** @return list<class-string<Node>> */
4357
public function getNodeTypes(): array
4458
{
45-
return [Expr::class];
59+
return [FuncCall::class, Identical::class, Equal::class, NotIdentical::class, NotEqual::class];
4660
}
4761

48-
public function refactor(Node $node): MethodCall|StaticCall|null
62+
/** @param FuncCall|Identical|Equal|NotIdentical|NotEqual $node */
63+
public function refactor(Node $node): ?Expr
4964
{
50-
if (! $node instanceof Identical && ! $node instanceof Equal) {
65+
if ($node instanceof FuncCall) {
66+
[$methodCall, $otherNode] = $this->handleFuncCall($node);
67+
} else {
68+
[$methodCall, $otherNode] = $this->handleBinaryOp($node);
69+
}
70+
71+
if ($methodCall === null || $otherNode === null) {
5172
return null;
5273
}
5374

54-
/** @var MethodCall|StaticCall|null $methodCall */
75+
$methodName = null;
76+
77+
if ($otherNode instanceof String_) {
78+
$methodName = $this->getMethodName($methodCall, $otherNode->value);
79+
}
80+
81+
if ($methodName === null) {
82+
$methodCall->args[] = new Arg($otherNode);
83+
} else {
84+
$methodCall->name = new Identifier($methodName);
85+
}
86+
87+
if ($node instanceof NotIdentical || $node instanceof NotEqual) {
88+
return new BooleanNot($methodCall);
89+
}
90+
91+
return $methodCall;
92+
}
93+
94+
/** @return array{0: MethodCall|StaticCall|null, 1: Expr|null} */
95+
private function handleFuncCall(FuncCall $funcCall): array
96+
{
97+
if (! $this->isName($funcCall->name, 'in_array')) {
98+
return [null, null];
99+
}
100+
101+
/** @todo remove and use ->getArg() instead when rector is bundled with php-parser 5.6 */
102+
$methodCall = $this->getArg($funcCall, 'needle', 0);
103+
$haystack = $this->getArg($funcCall, 'haystack', 1);
104+
// $methodCall = $funcCall->getArg('needle', 0);
105+
// $haystack = $funcCall->getArg('haystack', 1);
106+
107+
if (! $haystack instanceof Arg || ! $methodCall instanceof Arg || ! $this->validMethodCall($methodCall->value)) {
108+
return [null, null];
109+
}
110+
111+
return [$methodCall->value, $haystack->value];
112+
}
113+
114+
/** @return array{0: MethodCall|StaticCall|null, 1: Expr|null} */
115+
private function handleBinaryOp(BinaryOp $binaryOp): array
116+
{
55117
$methodCall = array_values(
56118
array_filter(
57-
[$node->left, $node->right],
58-
fn ($node) => ($node instanceof MethodCall || $node instanceof StaticCall) && $this->isName(
59-
$node->name,
60-
'environment'
61-
)
119+
[$binaryOp->left, $binaryOp->right],
120+
fn ($node) => $this->validMethodCall($node),
62121
)
63122
)[0] ?? null;
64123

65-
if ($methodCall === null || ! $this->validMethodCall($methodCall)) {
66-
return null;
67-
}
68-
69-
/** @var Expr $otherNode */
70124
$otherNode = array_values(
71-
array_filter([$node->left, $node->right], static fn ($node) => $node !== $methodCall)
125+
array_filter([$binaryOp->left, $binaryOp->right], static fn ($node) => $node !== $methodCall)
72126
)[0] ?? null;
73127

74-
if (! $otherNode instanceof String_) {
75-
return null;
76-
}
128+
return [$methodCall, $otherNode];
129+
}
77130

78-
// make sure the method call has no arguments
79-
if ($methodCall->getArgs() !== []) {
80-
return null;
131+
/** @phpstan-assert-if-true MethodCall|StaticCall $node */
132+
private function validMethodCall(Node $node): bool
133+
{
134+
if (! $node instanceof MethodCall && ! $node instanceof StaticCall) {
135+
return false;
81136
}
82137

83-
$methodCall->args[] = new Arg($otherNode);
138+
if (! $this->isName($node->name, 'environment')) {
139+
return false;
140+
}
84141

85-
return $methodCall;
86-
}
142+
// make sure the method call has no arguments
143+
if ($node->getArgs() !== []) {
144+
return false;
145+
}
87146

88-
private function validMethodCall(MethodCall|StaticCall $methodCall): bool
89-
{
90147
return match (true) {
91-
$methodCall instanceof MethodCall && $this->isObjectType(
92-
$methodCall->var,
148+
$node instanceof MethodCall && $this->isObjectType(
149+
$node->var,
93150
new ObjectType('Illuminate\Contracts\Foundation\Application')
94151
) => true,
95-
$methodCall instanceof StaticCall && $this->isObjectType(
96-
$methodCall->class,
152+
$node instanceof StaticCall && $this->isObjectType(
153+
$node->class,
97154
new ObjectType('Illuminate\Support\Facades\App')
98155
) => true,
99-
$methodCall instanceof StaticCall && $this->isObjectType(
100-
$methodCall->class,
156+
$node instanceof StaticCall && $this->isObjectType(
157+
$node->class,
101158
new ObjectType('App')
102159
) => true,
103160
default => false,
104161
};
105162
}
163+
164+
private function getMethodName(MethodCall|StaticCall $methodCall, string $environment): ?string
165+
{
166+
if (
167+
$methodCall instanceof MethodCall
168+
&& ! $this->isObjectType($methodCall->var, new ObjectType('Illuminate\Foundation\Application'))
169+
) {
170+
return null;
171+
}
172+
173+
return match ($environment) {
174+
'local' => 'isLocal',
175+
'production' => 'isProduction',
176+
default => null,
177+
};
178+
}
179+
180+
/** @todo remove and use ->getArg() instead when rector is bundled with php-parser 5.6 */
181+
private function getArg(CallLike $callLike, string $name, int $position): ?Arg
182+
{
183+
if ($callLike->isFirstClassCallable()) {
184+
return null;
185+
}
186+
foreach ($callLike->getArgs() as $i => $arg) {
187+
if ($arg->unpack) {
188+
continue;
189+
}
190+
if (
191+
($arg->name !== null && $arg->name->toString() === $name)
192+
|| ($arg->name === null && $i === $position)
193+
) {
194+
return $arg;
195+
}
196+
}
197+
198+
return null;
199+
}
106200
}

tests/Rector/Expr/AppEnvironmentComparisonToParameterRector/Fixture/fixture.php.inc

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@
22

33
namespace RectorLaravel\Tests\Rector\Expr\AppEnvironmentComparisonToParameterRector\Fixture;
44

5-
/** @var \Illuminate\Contracts\Foundation\Application $app */
5+
/** @var \Illuminate\Foundation\Application $app */
6+
/** @var \Illuminate\Contracts\Foundation\Application $appContract */
7+
$app->environment() === 'local';
8+
$app->environment() === 'staging';
69
$app->environment() === 'production';
10+
$appContract->environment() === 'local';
11+
$appContract->environment() === 'staging';
12+
$appContract->environment() === 'production';
713
'staging' == $app->environment();
14+
'testing' != $app->environment();
815

9-
if ($app->environment() === 'production') {
16+
if ($app->environment() !== 'production') {
1017
}
1118

19+
\Illuminate\Support\Facades\App::environment() === 'local';
20+
\Illuminate\Support\Facades\App::environment() === 'staging';
1221
\Illuminate\Support\Facades\App::environment() === 'production';
22+
\App::environment() === 'local';
23+
\App::environment() === 'staging';
1324
\App::environment() === 'production';
1425

1526
?>
@@ -18,14 +29,25 @@ if ($app->environment() === 'production') {
1829

1930
namespace RectorLaravel\Tests\Rector\Expr\AppEnvironmentComparisonToParameterRector\Fixture;
2031

21-
/** @var \Illuminate\Contracts\Foundation\Application $app */
22-
$app->environment('production');
32+
/** @var \Illuminate\Foundation\Application $app */
33+
/** @var \Illuminate\Contracts\Foundation\Application $appContract */
34+
$app->isLocal();
2335
$app->environment('staging');
36+
$app->isProduction();
37+
$appContract->environment('local');
38+
$appContract->environment('staging');
39+
$appContract->environment('production');
40+
$app->environment('staging');
41+
!$app->environment('testing');
2442

25-
if ($app->environment('production')) {
43+
if (!$app->isProduction()) {
2644
}
2745

28-
\Illuminate\Support\Facades\App::environment('production');
29-
\App::environment('production');
46+
\Illuminate\Support\Facades\App::isLocal();
47+
\Illuminate\Support\Facades\App::environment('staging');
48+
\Illuminate\Support\Facades\App::isProduction();
49+
\App::isLocal();
50+
\App::environment('staging');
51+
\App::isProduction();
3052

3153
?>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Expr\AppEnvironmentComparisonToParameterRector\Fixture;
4+
5+
/** @var \Illuminate\Foundation\Application $app */
6+
/** @var \Illuminate\Contracts\Foundation\Application $appContract */
7+
in_array($app->environment(), ['local']);
8+
in_array($appContract->environment(), ['testing'], true);
9+
10+
if (! in_array($app->environment(), ['local', 'testing'], true)) {
11+
}
12+
13+
in_array(\Illuminate\Support\Facades\App::environment(), ['local', 'testing']);
14+
in_array(\App::environment(), ['local', 'testing']);
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace RectorLaravel\Tests\Rector\Expr\AppEnvironmentComparisonToParameterRector\Fixture;
21+
22+
/** @var \Illuminate\Foundation\Application $app */
23+
/** @var \Illuminate\Contracts\Foundation\Application $appContract */
24+
$app->environment(['local']);
25+
$appContract->environment(['testing']);
26+
27+
if (! $app->environment(['local', 'testing'])) {
28+
}
29+
30+
\Illuminate\Support\Facades\App::environment(['local', 'testing']);
31+
\App::environment(['local', 'testing']);
32+
33+
?>

0 commit comments

Comments
 (0)