Passed
Push — main ( f49442...c8e194 )
by mikhail
51s
created

AddNamedArgumentsRector::getRuleDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
ccs 0
cts 5
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SavinMikhail\AddNamedArgumentsRector;
6
7
use InvalidArgumentException;
8
use PhpParser\Node;
9
use PhpParser\ConstExprEvaluator;
10
use PhpParser\Node\Arg;
11
use PhpParser\Node\Expr\FuncCall;
12
use PhpParser\Node\Expr\MethodCall;
13
use PhpParser\Node\Expr\New_;
14
use PhpParser\Node\Expr\StaticCall;
15
use PhpParser\Node\Identifier;
16
use PHPStan\Reflection\ExtendedParameterReflection;
0 ignored issues
show
Bug introduced by
The type PHPStan\Reflection\ExtendedParameterReflection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use PHPStan\Reflection\ReflectionProvider;
0 ignored issues
show
Bug introduced by
The type PHPStan\Reflection\ReflectionProvider was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use Rector\Contract\Rector\ConfigurableRectorInterface;
19
use Rector\NodeNameResolver\NodeNameResolver;
20
use Rector\NodeTypeResolver\NodeTypeResolver;
21
use Rector\Rector\AbstractRector;
22
use Rector\ValueObject\PhpVersion;
23
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
24
use SavinMikhail\AddNamedArgumentsRector\Config\ConfigStrategy;
25
use SavinMikhail\AddNamedArgumentsRector\Config\DefaultStrategy;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\AddNamedArg...\Config\DefaultStrategy was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use SavinMikhail\AddNamedArgumentsRector\Reflection\Reflection;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\AddNamedArg...r\Reflection\Reflection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
28
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
29
use Webmozart\Assert\Assert;
30
31
use function count;
32
33
/**
34
 * @see AddNamedArgumentsRectorTest
35
 */
36
final class AddNamedArgumentsRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface
37
{
38
    private string $configStrategy = DefaultStrategy::class;
39
40
    private readonly Reflection $reflectionService;
41
42
    private readonly ConstExprEvaluator $constExprEvaluator;
43
44 1
    public function __construct(
45
        ReflectionProvider $reflectionProvider,
46
        NodeNameResolver $nodeNameResolver,
47
        NodeTypeResolver $nodeTypeResolver,
48
        ?Reflection $reflectionService = null,
49
        ?ConstExprEvaluator $constExprEvaluator = null,
50
    ) {
51 1
        if ($reflectionService === null) {
52
            $reflectionService = new Reflection(
53
                reflectionProvider: $reflectionProvider,
54
                nodeNameResolver: $nodeNameResolver,
55
                nodeTypeResolver: $nodeTypeResolver,
56
            );
57
        }
58 1
        $this->reflectionService = $reflectionService;
0 ignored issues
show
Bug introduced by
The property reflectionService is declared read-only in SavinMikhail\AddNamedArg...AddNamedArgumentsRector.
Loading history...
59 1
        $this->constExprEvaluator = $constExprEvaluator ?? new ConstExprEvaluator(static function (string $name) {
0 ignored issues
show
Bug introduced by
The property constExprEvaluator is declared read-only in SavinMikhail\AddNamedArg...AddNamedArgumentsRector.
Loading history...
60
            if (\defined($name)) {
61
                return \constant($name);
62
            }
63
64
            throw new \RuntimeException("Undefined constant: {$name}");
65 1
        });
66
    }
67
68
    public function getRuleDefinition(): RuleDefinition
69
    {
70
        return new RuleDefinition('Convert all arguments to named arguments', codeSamples: [
71
            new CodeSample(
72
                badCode: '$user->setPassword("123456");',
73
                goodCode: '$user->changePassword(password: "123456");',
74
            ),
75
        ]);
76
    }
77
78 11
    public function getNodeTypes(): array
79
    {
80 11
        return [FuncCall::class, StaticCall::class, MethodCall::class, New_::class];
81
    }
82
83 11
    public function refactor(Node $node): ?Node
84
    {
85
        /** @var FuncCall|StaticCall|MethodCall|New_ $node */
86 11
        $parameters = $this->reflectionService->getParameters(node: $node);
87 11
        $classReflection = $this->reflectionService->getClassReflection(node: $node);
88
89 11
        if (!$this->configStrategy::shouldApply($node, $parameters, $classReflection)) {
90 6
            return null;
91
        }
92
93 5
        $this->addNamesToArgs(node: $node, parameters: $parameters);
94
95 5
        return $node;
96
    }
97
98
    /**
99
     * @param ExtendedParameterReflection[] $parameters
100
     */
101 5
    private function addNamesToArgs(
102
        FuncCall|StaticCall|MethodCall|New_ $node,
103
        array $parameters,
104
    ): void {
105 5
        $namedArgs = [];
106 5
        foreach ($node->args as $index => $arg) {
107 5
            $parameter = $parameters[$index] ?? null;
108 5
            if ($parameter === null) {
109
                $namedArgs[] = $arg;
110
                continue;
111
            }
112
113 5
            if ($this->shouldSkipArg($arg, $parameter)) {
0 ignored issues
show
Bug introduced by
It seems like $arg can also be of type PhpParser\Node\VariadicPlaceholder; however, parameter $arg of SavinMikhail\AddNamedArg...Rector::shouldSkipArg() does only seem to accept PhpParser\Node\Arg, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

113
            if ($this->shouldSkipArg(/** @scrutinizer ignore-type */ $arg, $parameter)) {
Loading history...
114 2
                continue;
115
            }
116
117 4
            $arg->name = new Identifier(name: $parameter->getName());
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on PhpParser\Node\VariadicPlaceholder.
Loading history...
118 4
            $namedArgs[] = $arg;
119
        }
120
121 5
        $node->args = $namedArgs;
122
    }
123
124 5
    private function shouldSkipArg(Arg $arg, ExtendedParameterReflection $parameter): bool
125
    {
126
        try {
127 5
            $defaultValue = $parameter->getDefaultValue();
128
        } catch (\Throwable) {
129
            return false;
130
        }
131
132
        try {
133 5
            $argValue = $this->constExprEvaluator->evaluateDirectly($arg->value);
134 1
        } catch (\Throwable) {
135 1
            return false;
136
        }
137
138 5
        if ($defaultValue instanceof \PHPStan\Type\ConstantScalarType) {
0 ignored issues
show
Bug introduced by
The type PHPStan\Type\ConstantScalarType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
139 2
            $defaultValue = $defaultValue->getValue();
140
        }
141 5
        return $argValue === $defaultValue;
142
    }
143
144 1
    public function provideMinPhpVersion(): int
145
    {
146 1
        return PhpVersion::PHP_80;
147
    }
148
149
    public function configure(array $configuration): void
150
    {
151
        Assert::lessThan(value: count(value: $configuration), limit: 2, message: 'You can pass only 1 strategy');
152
        if ($configuration === []) {
153
            return;
154
        }
155
        $strategyClass = $configuration[0];
156
157
        if (!class_exists(class: $strategyClass)) {
158
            throw new InvalidArgumentException(message: "Class {$strategyClass} does not exist.");
159
        }
160
161
        $strategy = new $strategyClass();
162
163
        Assert::isInstanceOf(value: $strategy, class: ConfigStrategy::class, message: 'Your strategy must implement ConfigStrategy interface');
164
165
        $this->configStrategy = $strategyClass;
166
    }
167
}
168