diff --git a/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/fixture_equal.php.inc b/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/loose_substr.php.inc similarity index 93% rename from rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/fixture_equal.php.inc rename to rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/loose_substr.php.inc index 8c7b7b41348..8975acc6dc6 100644 --- a/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/fixture_equal.php.inc +++ b/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/loose_substr.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\Php80\Rector\Identical\StrEndsWithRector\Fixture; -class Fixture +class LooseSubstr { public function run() { @@ -18,7 +18,7 @@ class Fixture namespace Rector\Tests\Php80\Rector\Identical\StrEndsWithRector\Fixture; -class Fixture +class LooseSubstr { public function run() { diff --git a/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/substr_not_equal_prefix.php.inc b/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/loose_substr_not_prefix.php.inc similarity index 90% rename from rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/substr_not_equal_prefix.php.inc rename to rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/loose_substr_not_prefix.php.inc index 5453a79542c..870389c18c1 100644 --- a/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/substr_not_equal_prefix.php.inc +++ b/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/loose_substr_not_prefix.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\Php80\Rector\Identical\StrEndsWithRector\Fixture; -class SubstrNotPrefix +class LooseSubstrNotPrefix { public function run() { @@ -18,7 +18,7 @@ class SubstrNotPrefix namespace Rector\Tests\Php80\Rector\Identical\StrEndsWithRector\Fixture; -class SubstrNotPrefix +class LooseSubstrNotPrefix { public function run() { diff --git a/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/skip_loose_numeric_string.php.inc b/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/skip_loose_numeric_string.php.inc new file mode 100644 index 00000000000..be2ba58390b --- /dev/null +++ b/rules-tests/Php80/Rector/Identical/StrEndsWithRector/Fixture/skip_loose_numeric_string.php.inc @@ -0,0 +1,14 @@ + diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/not_equal_substr.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_not_equal_substr.php.inc similarity index 88% rename from rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/not_equal_substr.php.inc rename to rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_not_equal_substr.php.inc index f48e15217a1..9061e790ada 100644 --- a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/not_equal_substr.php.inc +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_not_equal_substr.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\Php80\Rector\Identical\StrStartsWithRector\Fixture; -class NotIdenticalSubstr +class LooseNotEqualSubstr { public function run() { @@ -16,7 +16,7 @@ class NotIdenticalSubstr namespace Rector\Tests\Php80\Rector\Identical\StrStartsWithRector\Fixture; -class NotIdenticalSubstr +class LooseNotEqualSubstr { public function run() { diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/fixture_equal.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_substr.php.inc similarity index 91% rename from rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/fixture_equal.php.inc rename to rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_substr.php.inc index 5f87efcd522..ca62c06e012 100644 --- a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/fixture_equal.php.inc +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_substr.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\Php80\Rector\Identical\StrStartsWithRector\Fixture; -class Fixture +class LooseSubstr { public function run() { @@ -16,7 +16,7 @@ class Fixture namespace Rector\Tests\Php80\Rector\Identical\StrStartsWithRector\Fixture; -class Fixture +class LooseSubstr { public function run() { diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/other_side_equal.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_substr_other_side.php.inc similarity index 90% rename from rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/other_side_equal.php.inc rename to rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_substr_other_side.php.inc index 70ca84ee897..254589cfa36 100644 --- a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/other_side_equal.php.inc +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/loose_substr_other_side.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\Php80\Rector\Identical\StrStartsWithRector\Fixture; -class OtherSide +class LooseSubstrOtherSide { public function run() { @@ -18,7 +18,7 @@ class OtherSide namespace Rector\Tests\Php80\Rector\Identical\StrStartsWithRector\Fixture; -class OtherSide +class LooseSubstrOtherSide { public function run() { diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_numeric_string.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_numeric_string.php.inc new file mode 100644 index 00000000000..641996d5f30 --- /dev/null +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_numeric_string.php.inc @@ -0,0 +1,14 @@ + diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos.php.inc new file mode 100644 index 00000000000..1b91f7a0681 --- /dev/null +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos.php.inc @@ -0,0 +1,13 @@ + diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_hardcoded.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_hardcoded.php.inc new file mode 100644 index 00000000000..66108c96325 --- /dev/null +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_hardcoded.php.inc @@ -0,0 +1,13 @@ + diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_not_prefix.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_not_prefix.php.inc new file mode 100644 index 00000000000..044ad29bbae --- /dev/null +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_not_prefix.php.inc @@ -0,0 +1,13 @@ + diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_other_side.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_other_side.php.inc new file mode 100644 index 00000000000..76cd1b6f3ee --- /dev/null +++ b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/skip_loose_strpos_other_side.php.inc @@ -0,0 +1,13 @@ + diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal.php.inc deleted file mode 100644 index cd671374ff5..00000000000 --- a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal_not_prefix.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal_not_prefix.php.inc deleted file mode 100644 index 36f6112dbee..00000000000 --- a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal_not_prefix.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal_other_side.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal_other_side.php.inc deleted file mode 100644 index 1e959bb197d..00000000000 --- a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_equal_other_side.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_hardcoded_equal.php.inc b/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_hardcoded_equal.php.inc deleted file mode 100644 index a19bf55f32e..00000000000 --- a/rules-tests/Php80/Rector/Identical/StrStartsWithRector/Fixture/strpos_hardcoded_equal.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/StrposMatchAndRefactor.php b/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/StrposMatchAndRefactor.php index 55912c58310..a38f3d902d5 100644 --- a/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/StrposMatchAndRefactor.php +++ b/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/StrposMatchAndRefactor.php @@ -30,7 +30,12 @@ public function __construct( public function match(Identical|NotIdentical|Equal|NotEqual $binaryOp): ?StrStartsWith { - $isPositive = $binaryOp instanceof Identical || $binaryOp instanceof Equal; + // strpos() returns false on no match, and false == 0 + if ($binaryOp instanceof Equal || $binaryOp instanceof NotEqual) { + return null; + } + + $isPositive = $binaryOp instanceof Identical; if ($binaryOp->left instanceof FuncCall && $this->nodeNameResolver->isName($binaryOp->left, 'strpos')) { return $this->processBinaryOpLeft($binaryOp, $isPositive); diff --git a/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/SubstrMatchAndRefactor.php b/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/SubstrMatchAndRefactor.php index 344f24d4268..4b9f199877f 100644 --- a/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/SubstrMatchAndRefactor.php +++ b/rules/Php80/MatchAndRefactor/StrStartsWithMatchAndRefactor/SubstrMatchAndRefactor.php @@ -5,6 +5,7 @@ namespace Rector\Php80\MatchAndRefactor\StrStartsWithMatchAndRefactor; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\Equal; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\BinaryOp\NotEqual; @@ -34,6 +35,10 @@ public function match(Identical|NotIdentical|Equal|NotEqual $binaryOp): ?StrStar $isPositive = $binaryOp instanceof Identical || $binaryOp instanceof Equal; if ($binaryOp->left instanceof FuncCall && $this->nodeNameResolver->isName($binaryOp->left, 'substr')) { + if (! $this->isRefactorableLooseComparison($binaryOp, $binaryOp->right)) { + return null; + } + $funcCall = $binaryOp->left; $haystack = $funcCall->getArgs()[0] @@ -43,6 +48,10 @@ public function match(Identical|NotIdentical|Equal|NotEqual $binaryOp): ?StrStar } if ($binaryOp->right instanceof FuncCall && $this->nodeNameResolver->isName($binaryOp->right, 'substr')) { + if (! $this->isRefactorableLooseComparison($binaryOp, $binaryOp->left)) { + return null; + } + $funcCall = $binaryOp->right; $haystack = $funcCall->getArgs()[0] @@ -127,4 +136,19 @@ private function isHardcodedStringWithLNumberLength(StrStartsWith $strStartsWith return $lNumberLength->value === strlen($expr->value); } + + /** + * Loose comparison of two numeric string literals is numeric, e.g. '+1' == '01'; + * skip only that case, a variable needle is kept + */ + private function isRefactorableLooseComparison( + Identical|NotIdentical|Equal|NotEqual $binaryOp, + Expr $comparedExpr + ): bool { + if (! $binaryOp instanceof Equal && ! $binaryOp instanceof NotEqual) { + return true; + } + + return !$comparedExpr instanceof String_ || !is_numeric($comparedExpr->value); + } } diff --git a/rules/Php80/Rector/Identical/StrEndsWithRector.php b/rules/Php80/Rector/Identical/StrEndsWithRector.php index eac7487378d..e7b0dce4889 100644 --- a/rules/Php80/Rector/Identical/StrEndsWithRector.php +++ b/rules/Php80/Rector/Identical/StrEndsWithRector.php @@ -142,6 +142,10 @@ private function refactorSubstr(BinaryOp $binaryOp): FuncCall|BooleanNot|null return null; } + if (! $this->isRefactorableLooseComparison($binaryOp, $comparedNeedleExpr)) { + return null; + } + if (count($substrFuncCall->getArgs()) < 2) { return null; } @@ -252,6 +256,19 @@ private function isHardCodedLNumberAndString(Expr $substrOffset, Expr $needle): return $lNumber->value === strlen($needle->value); } + /** + * Loose comparison of two numeric string literals is numeric, e.g. '+1' == '01'; + * skip only that case, a variable needle is kept + */ + private function isRefactorableLooseComparison(BinaryOp $binaryOp, Expr $comparedExpr): bool + { + if (! $binaryOp instanceof Equal && ! $binaryOp instanceof NotEqual) { + return true; + } + + return !$comparedExpr instanceof String_ || !is_numeric($comparedExpr->value); + } + private function buildReturnNode(Expr $haystack, Expr $needle, bool $isPositive): FuncCall|BooleanNot { $funcCall = $this->nodeFactory->createFuncCall('str_ends_with', [$haystack, $needle]);