diff --git a/config/set/php81.php b/config/set/php81.php index b87051387c8..a2f856a1c9c 100644 --- a/config/set/php81.php +++ b/config/set/php81.php @@ -6,6 +6,7 @@ use Rector\Php81\Rector\Array_\FirstClassCallableRector; use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector; use Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector; +use Rector\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; use Rector\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector; use Rector\Php81\Rector\MethodCall\RemoveReflectionSetAccessibleCallsRector; @@ -24,6 +25,7 @@ SpatieEnumClassToEnumRector::class, SpatieEnumMethodCallToEnumConstRector::class, NullToStrictStringFuncCallArgRector::class, + NullToStrictIntPregSlitFuncCallLimitArgRector::class, FirstClassCallableRector::class, RemoveReflectionSetAccessibleCallsRector::class, ]); diff --git a/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/Fixture/fixture.php.inc b/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..ee7ae7a818b --- /dev/null +++ b/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/Fixture/fixture.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/Fixture/skip_no_limit.php.inc b/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/Fixture/skip_no_limit.php.inc new file mode 100644 index 00000000000..f0ca2ac91fa --- /dev/null +++ b/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/Fixture/skip_no_limit.php.inc @@ -0,0 +1,11 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/config/configured_rule.php b/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/config/configured_rule.php new file mode 100644 index 00000000000..7a7a0f036fa --- /dev/null +++ b/rules-tests/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([NullToStrictIntPregSlitFuncCallLimitArgRector::class]); diff --git a/rules/Php81/NodeManipulator/NullToStrictStringConverter.php b/rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php similarity index 78% rename from rules/Php81/NodeManipulator/NullToStrictStringConverter.php rename to rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php index f11cbec523e..10be1ac45c0 100644 --- a/rules/Php81/NodeManipulator/NullToStrictStringConverter.php +++ b/rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php @@ -6,10 +6,12 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Cast\Int_ as CastInt_; use PhpParser\Node\Expr\Cast\String_ as CastString_; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Ternary; +use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Scalar\InterpolatedString; use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; @@ -24,7 +26,7 @@ use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\PhpParser\Node\Value\ValueResolver; -final readonly class NullToStrictStringConverter +final readonly class NullToStrictStringIntConverter { public function __construct( private ValueResolver $valueResolver, @@ -42,7 +44,8 @@ public function convertIfNull( int $position, bool $isTrait, Scope $scope, - ParametersAcceptor $parametersAcceptor + ParametersAcceptor $parametersAcceptor, + string $targetType = 'string' ): ?FuncCall { if (! isset($args[$position])) { return null; @@ -50,12 +53,12 @@ public function convertIfNull( $argValue = $args[$position]->value; if ($this->valueResolver->isNull($argValue)) { - $args[$position]->value = new String_(''); + $args[$position]->value = $targetType === 'string' ? new String_('') : new Int_(0); $funcCall->args = $args; return $funcCall; } - if ($this->shouldSkipValue($argValue, $scope, $isTrait)) { + if ($this->shouldSkipValue($argValue, $scope, $isTrait, $targetType)) { return null; } @@ -67,11 +70,13 @@ public function convertIfNull( } } - if ($argValue instanceof Ternary && ! $this->shouldSkipValue($argValue->else, $scope, $isTrait)) { + if ($argValue instanceof Ternary && ! $this->shouldSkipValue($argValue->else, $scope, $isTrait, $targetType)) { if ($this->valueResolver->isNull($argValue->else)) { - $argValue->else = new String_(''); + $argValue->else = $targetType === 'string' ? new String_('') : new Int_(0); } else { - $argValue->else = new CastString_($argValue->else); + $argValue->else = $targetType === 'string' ? new CastString_($argValue->else) : new CastInt_( + $argValue->else + ); } $args[$position]->value = $argValue; @@ -79,20 +84,28 @@ public function convertIfNull( return $funcCall; } - $args[$position]->value = new CastString_($argValue); + $args[$position]->value = $targetType === 'string' ? new CastString_($argValue) : new CastInt_($argValue); $funcCall->args = $args; return $funcCall; } - private function shouldSkipValue(Expr $expr, Scope $scope, bool $isTrait): bool + private function shouldSkipValue(Expr $expr, Scope $scope, bool $isTrait, string $targetType): bool { $type = $this->nodeTypeResolver->getType($expr); - if ($type->isString()->yes()) { + if ($type->isString()->yes() && $targetType === 'string') { + return true; + } + + if ($type->isInteger()->yes() && $targetType === 'int') { return true; } $nativeType = $this->nodeTypeResolver->getNativeType($expr); - if ($nativeType->isString()->yes()) { + if ($nativeType->isString()->yes() && $targetType === 'string') { + return true; + } + + if ($nativeType->isInteger()->yes() && $targetType === 'int') { return true; } diff --git a/rules/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector.php b/rules/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector.php new file mode 100644 index 00000000000..db3b77d0e91 --- /dev/null +++ b/rules/Php81/Rector/FuncCall/NullToStrictIntPregSlitFuncCallLimitArgRector.php @@ -0,0 +1,162 @@ +> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkip($node)) { + return null; + } + + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return null; + } + + $args = $node->getArgs(); + $position = $this->argsAnalyzer->hasNamedArg($args) + ? $this->resolveNamedPosition($args) + : 2; + + if ($position === null) { + return null; + } + + if (! isset($args[$position])) { + return null; + } + + $classReflection = $scope->getClassReflection(); + $isTrait = $classReflection instanceof ClassReflection && $classReflection->isTrait(); + + $functionReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); + if (! $functionReflection instanceof FunctionReflection) { + return null; + } + + $parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select($functionReflection, $node, $scope); + $result = $this->nullToStrictStringIntConverter->convertIfNull( + $node, + $args, + $position, + $isTrait, + $scope, + $parametersAcceptor, + 'int' + ); + + if ($result instanceof Node) { + return $result; + } + + return null; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::DEPRECATE_NULL_ARG_IN_STRING_FUNCTION; + } + + /** + * @param Arg[] $args + */ + private function resolveNamedPosition(array $args): ?int + { + foreach ($args as $position => $arg) { + if (! $arg->name instanceof Identifier) { + continue; + } + + if (! $this->isName($arg->name, 'limit')) { + continue; + } + + return $position; + } + + return null; + } + + private function shouldSkip(FuncCall $funcCall): bool + { + if (! $this->isName($funcCall, 'preg_split')) { + return true; + } + + return $funcCall->isFirstClassCallable(); + } +} diff --git a/rules/Php81/Rector/FuncCall/NullToStrictStringFuncCallArgRector.php b/rules/Php81/Rector/FuncCall/NullToStrictStringFuncCallArgRector.php index b4b177ee061..42abbafa38d 100644 --- a/rules/Php81/Rector/FuncCall/NullToStrictStringFuncCallArgRector.php +++ b/rules/Php81/Rector/FuncCall/NullToStrictStringFuncCallArgRector.php @@ -16,7 +16,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper; use Rector\Php81\Enum\NameNullToStrictNullFunctionMap; -use Rector\Php81\NodeManipulator\NullToStrictStringConverter; +use Rector\Php81\NodeManipulator\NullToStrictStringIntConverter; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\ValueObject\PhpVersionFeature; @@ -32,7 +32,7 @@ final class NullToStrictStringFuncCallArgRector extends AbstractRector implement public function __construct( private readonly ReflectionResolver $reflectionResolver, private readonly ArgsAnalyzer $argsAnalyzer, - private readonly NullToStrictStringConverter $nullToStrictStringConverter + private readonly NullToStrictStringIntConverter $nullToStrictStringIntConverter ) { } @@ -109,7 +109,7 @@ public function refactor(Node $node): ?Node $isChanged = false; foreach ($positions as $position) { - $result = $this->nullToStrictStringConverter->convertIfNull( + $result = $this->nullToStrictStringIntConverter->convertIfNull( $node, $args, (int) $position, diff --git a/rules/Php85/Rector/FuncCall/ArrayKeyExistsNullToEmptyStringRector.php b/rules/Php85/Rector/FuncCall/ArrayKeyExistsNullToEmptyStringRector.php index b061f553d50..9f2f649ead6 100644 --- a/rules/Php85/Rector/FuncCall/ArrayKeyExistsNullToEmptyStringRector.php +++ b/rules/Php85/Rector/FuncCall/ArrayKeyExistsNullToEmptyStringRector.php @@ -13,7 +13,7 @@ use PHPStan\Reflection\FunctionReflection; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper; -use Rector\Php81\NodeManipulator\NullToStrictStringConverter; +use Rector\Php81\NodeManipulator\NullToStrictStringIntConverter; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\ValueObject\PhpVersionFeature; @@ -29,7 +29,7 @@ final class ArrayKeyExistsNullToEmptyStringRector extends AbstractRector impleme { public function __construct( private readonly ReflectionResolver $reflectionResolver, - private readonly NullToStrictStringConverter $nullToStrictStringConverter + private readonly NullToStrictStringIntConverter $nullToStrictStringIntConverter ) { } @@ -90,7 +90,7 @@ public function refactor(Node $node): ?Node $parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select($functionReflection, $node, $scope); - $result = $this->nullToStrictStringConverter->convertIfNull( + $result = $this->nullToStrictStringIntConverter->convertIfNull( $node, $args, $this->resolvePosition($args),