Skip to content

Commit 87a4c23

Browse files
authored
substring to Str::startsWith or Str::endsWith (#116)
* substring to Str::startsWith or Str::endsWith
1 parent 2f56b55 commit 87a4c23

File tree

7 files changed

+245
-0
lines changed

7 files changed

+245
-0
lines changed

docs/rector_rules_overview.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,19 @@ return static function (RectorConfig $rectorConfig): void {
702702

703703
<br>
704704

705+
## SubStrToStartsWithOrEndsWithStaticMethodCallRector
706+
707+
Change `substr()` to `startsWith()` or `endsWith()` static method call where applicable.
708+
709+
- class: [`RectorLaravel\Rector\FuncCall\SubStrToStartsWithOrEndsWithStaticMethodCallRector`](../src/Rector/Expr/SubStrToStartsWithOrEndsWithStaticMethodCallRector/SubStrToStartsWithOrEndsWithStaticMethodCallRector.php)
710+
711+
```diff
712+
-$string = substr($string, 0, 5) === 'foo';
713+
+$string = Str::startsWith($string, 'foo');
714+
```
715+
716+
<br>
717+
705718
## UnifyModelDatesWithCastsRector
706719

707720
Unify Model `$dates` property with `$casts`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
namespace RectorLaravel\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\StaticCall;
8+
use Rector\Core\Rector\AbstractRector;
9+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
10+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
11+
12+
/**
13+
* @see \RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\SubStrToStartsWithOrEndsWithStaticMethodCallRectorTest
14+
*/
15+
class SubStrToStartsWithOrEndsWithStaticMethodCallRector extends AbstractRector
16+
{
17+
18+
public function getRuleDefinition(): RuleDefinition
19+
{
20+
return new RuleDefinition('Use Str::startsWith() or Str::endsWith() instead of substr() === $str', [
21+
new CodeSample(
22+
<<<'CODE_SAMPLE'
23+
if (substr($str, 0, 3) === 'foo') {
24+
// do something
25+
}
26+
CODE_SAMPLE,
27+
<<<'CODE_SAMPLE'
28+
if (Str::startsWith($str, 'foo')) {
29+
// do something
30+
}
31+
CODE_SAMPLE,
32+
),
33+
]);
34+
}
35+
36+
public function getNodeTypes(): array
37+
{
38+
return [Expr::class];
39+
}
40+
41+
/**
42+
* @param Expr $node
43+
*/
44+
public function refactor(Node $node): ?StaticCall
45+
{
46+
if (!$node instanceof Expr\BinaryOp\Identical && !$node instanceof Expr\BinaryOp\Equal) {
47+
return null;
48+
}
49+
50+
/** @var Expr\FuncCall|null $functionCall */
51+
$functionCall = array_values(array_filter([$node->left, $node->right], function ($node) {
52+
return $node instanceof Expr\FuncCall && $this->isName($node, 'substr');
53+
}))[0] ?? null;
54+
55+
if ($functionCall === null) {
56+
return null;
57+
}
58+
59+
/** @var Expr $otherNode */
60+
$otherNode = array_values(array_filter([$node->left, $node->right], static function ($node) use ($functionCall) {
61+
return $node !== $functionCall;
62+
}))[0] ?? null;
63+
64+
// get the function call second argument value
65+
if (count($functionCall->getArgs()) < 2) {
66+
return null;
67+
}
68+
69+
$secondArgument = $this->valueResolver->getValue($functionCall->getArgs()[1]->value);
70+
71+
if (!is_int($secondArgument)) {
72+
return null;
73+
}
74+
75+
if ($secondArgument < 0 && isset($functionCall->getArgs()[2])) {
76+
return null;
77+
}
78+
79+
$methodName = $this->getStaticMethodName($secondArgument);
80+
81+
if ($methodName === null) {
82+
return null;
83+
}
84+
85+
return $this->nodeFactory->createStaticCall('Illuminate\Support\Str', $methodName, [
86+
$functionCall->getArgs()[0]->value,
87+
$otherNode,
88+
]);
89+
}
90+
91+
protected function getStaticMethodName(int $secondArgument): ?string
92+
{
93+
if ($secondArgument === 0) {
94+
return 'startsWith';
95+
}
96+
97+
if ($secondArgument < 0) {
98+
return 'endsWith';
99+
}
100+
101+
return null;
102+
}
103+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\Fixture;
4+
5+
if (substr($value, 0, 1) === 'a') {
6+
return true;
7+
}
8+
9+
if (substr($value, -1) === 'a') {
10+
return true;
11+
}
12+
13+
if ('a' === substr($value, 0, 1)) {
14+
return true;
15+
}
16+
17+
if ($prefix === substr($value, 0, 1)) {
18+
return true;
19+
}
20+
21+
if (substr($value, 0, 1) == 'a') {
22+
return true;
23+
}
24+
25+
?>
26+
-----
27+
<?php
28+
29+
namespace RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\Fixture;
30+
31+
if (\Illuminate\Support\Str::startsWith($value, 'a')) {
32+
return true;
33+
}
34+
35+
if (\Illuminate\Support\Str::endsWith($value, 'a')) {
36+
return true;
37+
}
38+
39+
if (\Illuminate\Support\Str::startsWith($value, 'a')) {
40+
return true;
41+
}
42+
43+
if (\Illuminate\Support\Str::startsWith($value, $prefix)) {
44+
return true;
45+
}
46+
47+
if (\Illuminate\Support\Str::startsWith($value, 'a')) {
48+
return true;
49+
}
50+
51+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\Fixture;
4+
5+
if (substr($value, -1, 1) === 'a') {
6+
return true;
7+
}
8+
9+
?>
10+
-----
11+
<?php
12+
13+
namespace RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\Fixture;
14+
15+
if (substr($value, -1, 1) === 'a') {
16+
return true;
17+
}
18+
19+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\Fixture;
4+
5+
if (substr($value, 1) === 'a') {
6+
return true;
7+
}
8+
9+
?>
10+
-----
11+
<?php
12+
13+
namespace RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\Fixture;
14+
15+
if (substr($value, 1) === 'a') {
16+
return true;
17+
}
18+
19+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Tests\Rector\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class SubStrToStartsWithOrEndsWithStaticMethodCallRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 12 additions & 0 deletions
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\Expr\SubStrToStartsWithOrEndsWithStaticMethodCallRector\SubStrToStartsWithOrEndsWithStaticMethodCallRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');
10+
11+
$rectorConfig->rule(SubStrToStartsWithOrEndsWithStaticMethodCallRector::class);
12+
};

0 commit comments

Comments
 (0)