Skip to content

Commit d4d98be

Browse files
ArshidArshid
authored andcommitted
MbStrContainsRector
1 parent 084fd1f commit d4d98be

File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Php80\Rector\NotIdentical;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr;
10+
use PhpParser\Node\Expr\BinaryOp\Equal;
11+
use PhpParser\Node\Expr\BinaryOp\Identical;
12+
use PhpParser\Node\Expr\BinaryOp\NotEqual;
13+
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
14+
use PhpParser\Node\Expr\BooleanNot;
15+
use PhpParser\Node\Expr\FuncCall;
16+
use PhpParser\Node\Name;
17+
use PhpParser\Node\Scalar\Int_;
18+
use Rector\Php80\NodeResolver\StrFalseComparisonResolver;
19+
use Rector\Rector\AbstractRector;
20+
use Rector\ValueObject\PhpVersionFeature;
21+
use Rector\ValueObject\PolyfillPackage;
22+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
23+
use Rector\VersionBonding\Contract\RelatedPolyfillInterface;
24+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
25+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
26+
27+
/**
28+
* @see \Rector\Tests\Php80\Rector\NotIdentical\MbStrContainsRector\MbStrContainsRectorTest
29+
*/
30+
final class MbStrContainsRector extends AbstractRector implements MinPhpVersionInterface, RelatedPolyfillInterface
31+
{
32+
/**
33+
* @var string[]
34+
*/
35+
private const OLD_STR_NAMES = ['mb_strpos', 'mb_strstr'];
36+
37+
public function __construct(
38+
private readonly StrFalseComparisonResolver $strFalseComparisonResolver
39+
) {
40+
}
41+
42+
public function provideMinPhpVersion(): int
43+
{
44+
return PhpVersionFeature::STR_CONTAINS;
45+
}
46+
47+
public function getRuleDefinition(): RuleDefinition
48+
{
49+
return new RuleDefinition(
50+
'Replace mb_strpos() !== false and mb_strstr() with str_contains()',
51+
[
52+
new CodeSample(
53+
<<<'CODE_SAMPLE'
54+
class SomeClass
55+
{
56+
public function run()
57+
{
58+
return strpos('abc', 'a') !== false;
59+
}
60+
}
61+
CODE_SAMPLE
62+
,
63+
<<<'CODE_SAMPLE'
64+
class SomeClass
65+
{
66+
public function run()
67+
{
68+
return str_contains('abc', 'a');
69+
}
70+
}
71+
CODE_SAMPLE
72+
),
73+
]
74+
);
75+
}
76+
77+
/**
78+
* @return array<class-string<Node>>
79+
*/
80+
public function getNodeTypes(): array
81+
{
82+
return [Identical::class, NotIdentical::class, Equal::class, NotEqual::class];
83+
}
84+
85+
/**
86+
* @param Identical|NotIdentical|Equal|NotEqual $node
87+
*/
88+
public function refactor(Node $node): ?Node
89+
{
90+
$funcCall = $this->strFalseComparisonResolver->resolve($node, self::OLD_STR_NAMES);
91+
92+
if (! $funcCall instanceof FuncCall) {
93+
return null;
94+
}
95+
96+
if ($funcCall->isFirstClassCallable()) {
97+
return null;
98+
}
99+
100+
if (isset($funcCall->getArgs()[2])) {
101+
$secondArg = $funcCall->getArgs()[2];
102+
103+
if ($this->isName($funcCall->name, 'mb_strpos') && ! $this->isIntegerZero($secondArg->value)) {
104+
$funcCall->args[0] = new Arg($this->nodeFactory->createFuncCall(
105+
'mb_substr',
106+
[$funcCall->args[0], $secondArg]
107+
));
108+
}
109+
110+
unset($funcCall->args[2]);
111+
}
112+
113+
$funcCall->name = new Name('str_contains');
114+
115+
if ($node instanceof Identical || $node instanceof Equal) {
116+
return new BooleanNot($funcCall);
117+
}
118+
119+
return $funcCall;
120+
}
121+
122+
public function providePolyfillPackage(): string
123+
{
124+
return PolyfillPackage::PHP_80;
125+
}
126+
127+
private function isIntegerZero(Expr $expr): bool
128+
{
129+
if (! $expr instanceof Int_) {
130+
return false;
131+
}
132+
133+
return $expr->value === 0;
134+
}
135+
}

0 commit comments

Comments
 (0)