AddNamedArgumentsRector::refactor()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 3
b 0
f 0
nc 2
nop 1
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SavinMikhail\AddNamedArgumentsRector;
6
7
use InvalidArgumentException;
8
use PhpParser\ConstExprEvaluator;
9
use PhpParser\Node;
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 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...
19
use Rector\Contract\Rector\ConfigurableRectorInterface;
20
use Rector\NodeNameResolver\NodeNameResolver;
21
use Rector\NodeTypeResolver\NodeTypeResolver;
22
use Rector\Rector\AbstractRector;
23
use Rector\ValueObject\PhpVersion;
24
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
25
use RuntimeException;
26
use SavinMikhail\AddNamedArgumentsRector\Config\ConfigStrategy;
27
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...
28
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...
29
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
30
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
31
use Throwable;
32
use Webmozart\Assert\Assert;
33
34
use function constant;
35
use function count;
36
use function defined;
37
38
/**
39
 * @see AddNamedArgumentsRectorTest
40
 */
41
final class AddNamedArgumentsRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface
42
{
43
    private string $configStrategy = DefaultStrategy::class;
44
45
    private readonly Reflection $reflectionService;
46
47
    private readonly ConstExprEvaluator $constExprEvaluator;
48
49 1
    public function __construct(
50
        ReflectionProvider $reflectionProvider,
51
        NodeNameResolver $nodeNameResolver,
52
        NodeTypeResolver $nodeTypeResolver,
53
        ?Reflection $reflectionService = null,
54
        ?ConstExprEvaluator $constExprEvaluator = null,
55
    ) {
56 1
        if ($reflectionService === null) {
57
            $reflectionService = new Reflection(
58
                reflectionProvider: $reflectionProvider,
59
                nodeNameResolver: $nodeNameResolver,
60
                nodeTypeResolver: $nodeTypeResolver,
61
            );
62
        }
63 1
        $this->reflectionService = $reflectionService;
0 ignored issues
show
Bug introduced by
The property reflectionService is declared read-only in SavinMikhail\AddNamedArg...AddNamedArgumentsRector.
Loading history...
64 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...
65
            if (defined($name)) {
66
                return constant($name);
67
            }
68
69
            throw new RuntimeException("Undefined constant: {$name}");
70 1
        });
71
    }
72
73
    public function getRuleDefinition(): RuleDefinition
74
    {
75
        return new RuleDefinition('Convert all arguments to named arguments', codeSamples: [
76
            new CodeSample(
77
                badCode: '$user->setPassword("123456");',
78
                goodCode: '$user->changePassword(password: "123456");',
79
            ),
80
        ]);
81
    }
82
83 11
    public function getNodeTypes(): array
84
    {
85 11
        return [FuncCall::class, StaticCall::class, MethodCall::class, New_::class];
86
    }
87
88 11
    public function refactor(Node $node): ?Node
89
    {
90
        /** @var FuncCall|StaticCall|MethodCall|New_ $node */
91 11
        $parameters = $this->reflectionService->getParameters(node: $node);
92 11
        $classReflection = $this->reflectionService->getClassReflection(node: $node);
93
94 11
        if (!$this->configStrategy::shouldApply($node, $parameters, $classReflection)) {
95 6
            return null;
96
        }
97
98 5
        $this->addNamesToArgs(node: $node, parameters: $parameters);
99
100 5
        return $node;
101
    }
102
103
    /**
104
     * @param ExtendedParameterReflection[] $parameters
105
     */
106 5
    private function addNamesToArgs(
107
        FuncCall|StaticCall|MethodCall|New_ $node,
108
        array $parameters,
109
    ): void {
110 5
        $namedArgs = [];
111 5
        foreach ($node->args as $index => $arg) {
112 5
            $parameter = $parameters[$index] ?? null;
113 5
            if ($parameter === null) {
114
                $namedArgs[] = $arg;
115
116
                continue;
117
            }
118
119 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

119
            if ($this->shouldSkipArg(/** @scrutinizer ignore-type */ $arg, $parameter)) {
Loading history...
120 1
                continue;
121
            }
122
123 5
            $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...
124 5
            $namedArgs[] = $arg;
125
        }
126
127 5
        $node->args = $namedArgs;
128
    }
129
130 5
    private function shouldSkipArg(Arg $arg, ExtendedParameterReflection $parameter): bool
131
    {
132
        try {
133 5
            $defaultValue = $parameter->getDefaultValue();
134
        } catch (Throwable) {
135
            return false;
136
        }
137
138
        try {
139 5
            $argValue = $this->constExprEvaluator->evaluateDirectly($arg->value);
140 1
        } catch (Throwable) {
141 1
            return false;
142
        }
143
144 5
        if ($defaultValue instanceof ConstantScalarType) {
145 2
            $defaultValue = $defaultValue->getValue();
146
        }
147
148 5
        return $argValue === $defaultValue;
149
    }
150
151 1
    public function provideMinPhpVersion(): int
152
    {
153 1
        return PhpVersion::PHP_80;
154
    }
155
156
    public function configure(array $configuration): void
157
    {
158
        Assert::lessThan(value: count(value: $configuration), limit: 2, message: 'You can pass only 1 strategy');
159
        if ($configuration === []) {
160
            return;
161
        }
162
        $strategyClass = $configuration[0];
163
164
        if (!class_exists(class: $strategyClass)) {
165
            throw new InvalidArgumentException(message: "Class {$strategyClass} does not exist.");
166
        }
167
168
        $strategy = new $strategyClass();
169
170
        Assert::isInstanceOf(value: $strategy, class: ConfigStrategy::class, message: 'Your strategy must implement ConfigStrategy interface');
171
172
        $this->configStrategy = $strategyClass;
173
    }
174
}
175