Skip to content

Commit f8d624d

Browse files
authored
Remove null coalesce for methods with default argument (#268)
* basic mvp * Working in all scenarios * All positive test cases * Improvement * Working fully * fixes * simplify the rule configuration * improved * Fix removal of default * Docs and added to CodeQuality set * add env function * use data_get and position argument * cs fix * Update rector_rules_overview.md
1 parent 65c14c5 commit f8d624d

10 files changed

+288
-1
lines changed

Diff for: config/sets/laravel-code-quality.php

+2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
use Rector\Config\RectorConfig;
66
use RectorLaravel\Rector\Assign\CallOnAppArrayAccessToStandaloneAssignRector;
7+
use RectorLaravel\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector;
78
use RectorLaravel\Rector\MethodCall\ReverseConditionableMethodCallRector;
89

910
return static function (RectorConfig $rectorConfig): void {
1011
$rectorConfig->import(__DIR__ . '/../config.php');
1112
$rectorConfig->rule(CallOnAppArrayAccessToStandaloneAssignRector::class);
1213
$rectorConfig->rule(ReverseConditionableMethodCallRector::class);
14+
$rectorConfig->rule(ApplyDefaultInsteadOfNullCoalesceRector::class);
1315
};

Diff for: docs/rector_rules_overview.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 71 Rules Overview
1+
# 72 Rules Overview
22

33
## AbortIfRector
44

@@ -221,6 +221,21 @@ Replace `$app->environment() === 'local'` with `$app->environment('local')`
221221

222222
<br>
223223

224+
## ApplyDefaultInsteadOfNullCoalesceRector
225+
226+
Apply default instead of null coalesce
227+
228+
:wrench: **configure it!**
229+
230+
- class: [`RectorLaravel\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector`](../src/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector.php)
231+
232+
```diff
233+
-custom_helper('app.name') ?? 'Laravel';
234+
+custom_helper('app.name', 'Laravel');
235+
```
236+
237+
<br>
238+
224239
## ArgumentFuncCallToMethodCallRector
225240

226241
Move help facade-like function calls to constructor injection
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
namespace RectorLaravel\Rector\Coalesce;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Arg;
7+
use PhpParser\Node\Expr\BinaryOp\Coalesce;
8+
use PhpParser\Node\Expr\FuncCall;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Expr\StaticCall;
11+
use PHPStan\Type\ObjectType;
12+
use Rector\Contract\Rector\ConfigurableRectorInterface;
13+
use Rector\Rector\AbstractRector;
14+
use RectorLaravel\ValueObject\ApplyDefaultInsteadOfNullCoalesce;
15+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
16+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
17+
use Webmozart\Assert\Assert;
18+
19+
/**
20+
* @see \RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector\ApplyDefaultInsteadOfNullCoalesceRectorTest
21+
*/
22+
final class ApplyDefaultInsteadOfNullCoalesceRector extends AbstractRector implements ConfigurableRectorInterface
23+
{
24+
/**
25+
* @var ApplyDefaultInsteadOfNullCoalesce[]
26+
*/
27+
private array $applyDefaultWith;
28+
29+
public function __construct()
30+
{
31+
$this->applyDefaultWith = self::defaultLaravelMethods();
32+
}
33+
34+
/**
35+
* @return ApplyDefaultInsteadOfNullCoalesce[]
36+
*/
37+
public static function defaultLaravelMethods(): array
38+
{
39+
return [
40+
new ApplyDefaultInsteadOfNullCoalesce('config'),
41+
new ApplyDefaultInsteadOfNullCoalesce('env'),
42+
new ApplyDefaultInsteadOfNullCoalesce('data_get', argumentPosition: 2),
43+
new ApplyDefaultInsteadOfNullCoalesce('input', new ObjectType('Illuminate\Http\Request')),
44+
new ApplyDefaultInsteadOfNullCoalesce('get', new ObjectType('Illuminate\Support\Env')),
45+
];
46+
}
47+
48+
public function getRuleDefinition(): RuleDefinition
49+
{
50+
return new RuleDefinition(
51+
'Apply default instead of null coalesce',
52+
[
53+
new ConfiguredCodeSample(
54+
<<<'CODE_SAMPLE'
55+
custom_helper('app.name') ?? 'Laravel';
56+
CODE_SAMPLE,
57+
<<<'CODE_SAMPLE'
58+
custom_helper('app.name', 'Laravel');
59+
CODE_SAMPLE
60+
,
61+
[
62+
...self::defaultLaravelMethods(),
63+
new ApplyDefaultInsteadOfNullCoalesce('custom_helper'),
64+
],
65+
),
66+
]
67+
);
68+
}
69+
70+
public function getNodeTypes(): array
71+
{
72+
return [Coalesce::class];
73+
}
74+
75+
/**
76+
* @param Coalesce $node
77+
*/
78+
public function refactor(Node $node): MethodCall|StaticCall|FuncCall|null
79+
{
80+
if (! $node->left instanceof FuncCall &&
81+
! $node->left instanceof MethodCall &&
82+
! $node->left instanceof StaticCall
83+
) {
84+
return null;
85+
}
86+
87+
if ($node->left->isFirstClassCallable()) {
88+
return null;
89+
}
90+
91+
$call = $node->left;
92+
93+
foreach ($this->applyDefaultWith as $applyDefaultWith) {
94+
$valid = false;
95+
96+
$objectType = $call->var ?? $call->class ?? null;
97+
98+
if (
99+
$applyDefaultWith->getObjectType() instanceof ObjectType &&
100+
$objectType !== null &&
101+
$this->isObjectType(
102+
$objectType,
103+
$applyDefaultWith->getObjectType()) &&
104+
$this->isName($call->name, $applyDefaultWith->getMethodName())
105+
) {
106+
$valid = true;
107+
} elseif (
108+
$applyDefaultWith->getObjectType() === null &&
109+
$this->isName($call->name, $applyDefaultWith->getMethodName())
110+
) {
111+
$valid = true;
112+
}
113+
114+
if (! $valid) {
115+
continue;
116+
}
117+
118+
if (count($call->args) === $applyDefaultWith->getArgumentPosition()) {
119+
if ($this->getType($node->right)->isNull()->yes()) {
120+
return $call;
121+
}
122+
$call->args[count($call->args)] = new Arg($node->right);
123+
124+
return $call;
125+
}
126+
}
127+
128+
return null;
129+
}
130+
131+
public function configure(array $configuration): void
132+
{
133+
Assert::allIsInstanceOf($configuration, ApplyDefaultInsteadOfNullCoalesce::class);
134+
$this->applyDefaultWith = $configuration;
135+
}
136+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace RectorLaravel\ValueObject;
4+
5+
use PHPStan\Type\ObjectType;
6+
7+
final readonly class ApplyDefaultInsteadOfNullCoalesce
8+
{
9+
public function __construct(private string $methodName, private ?ObjectType $objectType = null, private int $argumentPosition = 1)
10+
{
11+
}
12+
13+
public function getObjectType(): ?ObjectType
14+
{
15+
return $this->objectType;
16+
}
17+
18+
public function getMethodName(): string
19+
{
20+
return $this->methodName;
21+
}
22+
23+
public function getArgumentPosition(): int
24+
{
25+
return $this->argumentPosition;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class ApplyDefaultInsteadOfNullCoalesceRectorTest extends AbstractRectorTestCase
12+
{
13+
public static function provideData(): Iterator
14+
{
15+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
16+
}
17+
18+
/**
19+
* @test
20+
*/
21+
#[DataProvider('provideData')]
22+
public function test(string $filePath): void
23+
{
24+
$this->doTestFile($filePath);
25+
}
26+
27+
public function provideConfigFilePath(): string
28+
{
29+
return __DIR__ . '/config/configured_rule.php';
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector\Fixture;
4+
5+
data_get([], 'key') ?? false;
6+
7+
?>
8+
-----
9+
<?php
10+
11+
namespace RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector\Fixture;
12+
13+
data_get([], 'key', false);
14+
15+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector\Fixture;
4+
5+
config('app.name') ?? false;
6+
config('app.name') ?? null;
7+
8+
(new \Illuminate\Http\Request())->input('value') ?? '';
9+
10+
\Illuminate\Support\Env::get('APP_NAME') ?? '';
11+
12+
?>
13+
-----
14+
<?php
15+
16+
namespace RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector\Fixture;
17+
18+
config('app.name', false);
19+
config('app.name');
20+
21+
(new \Illuminate\Http\Request())->input('value', '');
22+
23+
\Illuminate\Support\Env::get('APP_NAME', '');
24+
25+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector\Fixture;
4+
5+
config('app.name', false) ?? false;
6+
7+
(new \Illuminate\Http\Request())->input('value', '') ?? '';
8+
9+
\Illuminate\Support\Env::get('APP_NAME', '') ?? '';
10+
11+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector\Fixture;
4+
5+
config1('app.name') ?? false;
6+
7+
(new \Illuminate\Http\Request())->inputOf('value') ?? '';
8+
(new \Illuminate\Http\RequestElse())->input('value') ?? '';
9+
10+
\Illuminate\Support\Env::getAgain('APP_NAME') ?? '';
11+
\Illuminate\Support\EnvUtil::get('APP_NAME') ?? '';
12+
13+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use RectorLaravel\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');
10+
11+
$rectorConfig->rule(ApplyDefaultInsteadOfNullCoalesceRector::class);
12+
};

0 commit comments

Comments
 (0)