Skip to content

Commit 667de49

Browse files
authored
[Php81] Add NullToStrictIntPregSlitFuncCallLimitArgRector (#7240)
* [Php81] Add NullToStrictIntPregSlitFuncCallLimitArgRector * rectify * fix * fix * fix * Fix * register
1 parent 4507adf commit 667de49

9 files changed

Lines changed: 269 additions & 17 deletions

File tree

config/set/php81.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Rector\Php81\Rector\Array_\FirstClassCallableRector;
77
use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector;
88
use Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector;
9+
use Rector\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector;
910
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
1011
use Rector\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector;
1112
use Rector\Php81\Rector\MethodCall\RemoveReflectionSetAccessibleCallsRector;
@@ -24,6 +25,7 @@
2425
SpatieEnumClassToEnumRector::class,
2526
SpatieEnumMethodCallToEnumConstRector::class,
2627
NullToStrictStringFuncCallArgRector::class,
28+
NullToStrictIntPregSlitFuncCallLimitArgRector::class,
2729
FirstClassCallableRector::class,
2830
RemoveReflectionSetAccessibleCallsRector::class,
2931
]);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Rector\Tests\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector\Fixture;
4+
5+
class Fixture
6+
{
7+
public function run($output)
8+
{
9+
preg_split('/\s/', $output, NULL, PREG_SPLIT_NO_EMPTY);
10+
}
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Tests\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector\Fixture;
18+
19+
class Fixture
20+
{
21+
public function run($output)
22+
{
23+
preg_split('/\s/', $output, 0, PREG_SPLIT_NO_EMPTY);
24+
}
25+
}
26+
27+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Rector\Tests\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector\Fixture;
4+
5+
class SkipNoLimit
6+
{
7+
public function run($output)
8+
{
9+
$keywords = preg_split("/[\s,]+/", $output);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class NullToStrictIntPregSlitFuncCallLimitArgRectorTest 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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([NullToStrictIntPregSlitFuncCallLimitArgRector::class]);

rules/Php81/NodeManipulator/NullToStrictStringConverter.php renamed to rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
use PhpParser\Node\Arg;
88
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\Cast\Int_ as CastInt_;
910
use PhpParser\Node\Expr\Cast\String_ as CastString_;
1011
use PhpParser\Node\Expr\FuncCall;
1112
use PhpParser\Node\Expr\MethodCall;
1213
use PhpParser\Node\Expr\Ternary;
14+
use PhpParser\Node\Scalar\Int_;
1315
use PhpParser\Node\Scalar\InterpolatedString;
1416
use PhpParser\Node\Scalar\String_;
1517
use PHPStan\Analyser\Scope;
@@ -24,7 +26,7 @@
2426
use Rector\NodeTypeResolver\NodeTypeResolver;
2527
use Rector\PhpParser\Node\Value\ValueResolver;
2628

27-
final readonly class NullToStrictStringConverter
29+
final readonly class NullToStrictStringIntConverter
2830
{
2931
public function __construct(
3032
private ValueResolver $valueResolver,
@@ -42,20 +44,21 @@ public function convertIfNull(
4244
int $position,
4345
bool $isTrait,
4446
Scope $scope,
45-
ParametersAcceptor $parametersAcceptor
47+
ParametersAcceptor $parametersAcceptor,
48+
string $targetType = 'string'
4649
): ?FuncCall {
4750
if (! isset($args[$position])) {
4851
return null;
4952
}
5053

5154
$argValue = $args[$position]->value;
5255
if ($this->valueResolver->isNull($argValue)) {
53-
$args[$position]->value = new String_('');
56+
$args[$position]->value = $targetType === 'string' ? new String_('') : new Int_(0);
5457
$funcCall->args = $args;
5558
return $funcCall;
5659
}
5760

58-
if ($this->shouldSkipValue($argValue, $scope, $isTrait)) {
61+
if ($this->shouldSkipValue($argValue, $scope, $isTrait, $targetType)) {
5962
return null;
6063
}
6164

@@ -67,32 +70,42 @@ public function convertIfNull(
6770
}
6871
}
6972

70-
if ($argValue instanceof Ternary && ! $this->shouldSkipValue($argValue->else, $scope, $isTrait)) {
73+
if ($argValue instanceof Ternary && ! $this->shouldSkipValue($argValue->else, $scope, $isTrait, $targetType)) {
7174
if ($this->valueResolver->isNull($argValue->else)) {
72-
$argValue->else = new String_('');
75+
$argValue->else = $targetType === 'string' ? new String_('') : new Int_(0);
7376
} else {
74-
$argValue->else = new CastString_($argValue->else);
77+
$argValue->else = $targetType === 'string' ? new CastString_($argValue->else) : new CastInt_(
78+
$argValue->else
79+
);
7580
}
7681

7782
$args[$position]->value = $argValue;
7883
$funcCall->args = $args;
7984
return $funcCall;
8085
}
8186

82-
$args[$position]->value = new CastString_($argValue);
87+
$args[$position]->value = $targetType === 'string' ? new CastString_($argValue) : new CastInt_($argValue);
8388
$funcCall->args = $args;
8489
return $funcCall;
8590
}
8691

87-
private function shouldSkipValue(Expr $expr, Scope $scope, bool $isTrait): bool
92+
private function shouldSkipValue(Expr $expr, Scope $scope, bool $isTrait, string $targetType): bool
8893
{
8994
$type = $this->nodeTypeResolver->getType($expr);
90-
if ($type->isString()->yes()) {
95+
if ($type->isString()->yes() && $targetType === 'string') {
96+
return true;
97+
}
98+
99+
if ($type->isInteger()->yes() && $targetType === 'int') {
91100
return true;
92101
}
93102

94103
$nativeType = $this->nodeTypeResolver->getNativeType($expr);
95-
if ($nativeType->isString()->yes()) {
104+
if ($nativeType->isString()->yes() && $targetType === 'string') {
105+
return true;
106+
}
107+
108+
if ($nativeType->isInteger()->yes() && $targetType === 'int') {
96109
return true;
97110
}
98111

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Php81\Rector\FuncCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr\FuncCall;
10+
use PhpParser\Node\Identifier;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Reflection\ClassReflection;
13+
use PHPStan\Reflection\FunctionReflection;
14+
use Rector\NodeAnalyzer\ArgsAnalyzer;
15+
use Rector\NodeTypeResolver\Node\AttributeKey;
16+
use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper;
17+
use Rector\Php81\NodeManipulator\NullToStrictStringIntConverter;
18+
use Rector\Rector\AbstractRector;
19+
use Rector\Reflection\ReflectionResolver;
20+
use Rector\ValueObject\PhpVersionFeature;
21+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
22+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
23+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
24+
25+
/**
26+
* @see https://3v4l.org/cVPim
27+
* @see \Rector\Tests\Php81\Rector\FuncCall\NullToStrictIntPregSlitFuncCallLimitArgRector\NullToStrictIntPregSlitFuncCallLimitArgRectorTest
28+
*/
29+
final class NullToStrictIntPregSlitFuncCallLimitArgRector extends AbstractRector implements MinPhpVersionInterface
30+
{
31+
public function __construct(
32+
private readonly ReflectionResolver $reflectionResolver,
33+
private readonly ArgsAnalyzer $argsAnalyzer,
34+
private readonly NullToStrictStringIntConverter $nullToStrictStringIntConverter
35+
) {
36+
}
37+
38+
public function getRuleDefinition(): RuleDefinition
39+
{
40+
return new RuleDefinition(
41+
'Change null to strict int defined preg_split limit arg function call argument',
42+
[
43+
new CodeSample(
44+
<<<'CODE_SAMPLE'
45+
class SomeClass
46+
{
47+
public function run()
48+
{
49+
preg_split('/\s/', $output, NULL, PREG_SPLIT_NO_EMPTY)
50+
}
51+
}
52+
CODE_SAMPLE
53+
,
54+
<<<'CODE_SAMPLE'
55+
class SomeClass
56+
{
57+
public function run()
58+
{
59+
preg_split('/\s/', $output, 0, PREG_SPLIT_NO_EMPTY)
60+
}
61+
}
62+
CODE_SAMPLE
63+
),
64+
]
65+
);
66+
}
67+
68+
/**
69+
* @return array<class-string<Node>>
70+
*/
71+
public function getNodeTypes(): array
72+
{
73+
return [FuncCall::class];
74+
}
75+
76+
/**
77+
* @param FuncCall $node
78+
*/
79+
public function refactor(Node $node): ?Node
80+
{
81+
if ($this->shouldSkip($node)) {
82+
return null;
83+
}
84+
85+
$scope = $node->getAttribute(AttributeKey::SCOPE);
86+
if (! $scope instanceof Scope) {
87+
return null;
88+
}
89+
90+
$args = $node->getArgs();
91+
$position = $this->argsAnalyzer->hasNamedArg($args)
92+
? $this->resolveNamedPosition($args)
93+
: 2;
94+
95+
if ($position === null) {
96+
return null;
97+
}
98+
99+
if (! isset($args[$position])) {
100+
return null;
101+
}
102+
103+
$classReflection = $scope->getClassReflection();
104+
$isTrait = $classReflection instanceof ClassReflection && $classReflection->isTrait();
105+
106+
$functionReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node);
107+
if (! $functionReflection instanceof FunctionReflection) {
108+
return null;
109+
}
110+
111+
$parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select($functionReflection, $node, $scope);
112+
$result = $this->nullToStrictStringIntConverter->convertIfNull(
113+
$node,
114+
$args,
115+
$position,
116+
$isTrait,
117+
$scope,
118+
$parametersAcceptor,
119+
'int'
120+
);
121+
122+
if ($result instanceof Node) {
123+
return $result;
124+
}
125+
126+
return null;
127+
}
128+
129+
public function provideMinPhpVersion(): int
130+
{
131+
return PhpVersionFeature::DEPRECATE_NULL_ARG_IN_STRING_FUNCTION;
132+
}
133+
134+
/**
135+
* @param Arg[] $args
136+
*/
137+
private function resolveNamedPosition(array $args): ?int
138+
{
139+
foreach ($args as $position => $arg) {
140+
if (! $arg->name instanceof Identifier) {
141+
continue;
142+
}
143+
144+
if (! $this->isName($arg->name, 'limit')) {
145+
continue;
146+
}
147+
148+
return $position;
149+
}
150+
151+
return null;
152+
}
153+
154+
private function shouldSkip(FuncCall $funcCall): bool
155+
{
156+
if (! $this->isName($funcCall, 'preg_split')) {
157+
return true;
158+
}
159+
160+
return $funcCall->isFirstClassCallable();
161+
}
162+
}

rules/Php81/Rector/FuncCall/NullToStrictStringFuncCallArgRector.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use Rector\NodeTypeResolver\Node\AttributeKey;
1717
use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper;
1818
use Rector\Php81\Enum\NameNullToStrictNullFunctionMap;
19-
use Rector\Php81\NodeManipulator\NullToStrictStringConverter;
19+
use Rector\Php81\NodeManipulator\NullToStrictStringIntConverter;
2020
use Rector\Rector\AbstractRector;
2121
use Rector\Reflection\ReflectionResolver;
2222
use Rector\ValueObject\PhpVersionFeature;
@@ -32,7 +32,7 @@ final class NullToStrictStringFuncCallArgRector extends AbstractRector implement
3232
public function __construct(
3333
private readonly ReflectionResolver $reflectionResolver,
3434
private readonly ArgsAnalyzer $argsAnalyzer,
35-
private readonly NullToStrictStringConverter $nullToStrictStringConverter
35+
private readonly NullToStrictStringIntConverter $nullToStrictStringIntConverter
3636
) {
3737
}
3838

@@ -109,7 +109,7 @@ public function refactor(Node $node): ?Node
109109
$isChanged = false;
110110

111111
foreach ($positions as $position) {
112-
$result = $this->nullToStrictStringConverter->convertIfNull(
112+
$result = $this->nullToStrictStringIntConverter->convertIfNull(
113113
$node,
114114
$args,
115115
(int) $position,

0 commit comments

Comments
 (0)