1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace SavinMikhail\AddNamedArgumentsRector\Config; |
||
6 | |||
7 | use PhpParser\Node; |
||
8 | use PhpParser\Node\Expr\FuncCall; |
||
9 | use PhpParser\Node\Expr\MethodCall; |
||
10 | use PhpParser\Node\Expr\New_; |
||
11 | use PhpParser\Node\Expr\StaticCall; |
||
12 | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; |
||
13 | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum; |
||
14 | use PHPStan\Reflection\ClassReflection; |
||
15 | use ReflectionFunctionAbstract; |
||
16 | use SavinMikhail\AddNamedArgumentsRector\Reflection\Reflection; |
||
17 | |||
18 | final readonly class DefaultStrategy implements ConfigStrategy |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
19 | { |
||
20 | 10 | public static function shouldApply( |
|
21 | FuncCall|StaticCall|MethodCall|New_ $node, |
||
22 | array $parameters, |
||
23 | ?ClassReflection $classReflection = null, |
||
24 | ): bool { |
||
25 | 10 | if (!self::areArgumentsSuitable($node->args, $parameters)) { |
|
26 | 5 | return false; |
|
27 | } |
||
28 | |||
29 | 5 | if ($classReflection !== null && !self::classAllowsNamedArguments($classReflection)) { |
|
30 | 1 | return false; |
|
31 | } |
||
32 | |||
33 | 4 | if (!self::functionAllowsNamedArguments($node, $classReflection)) { |
|
34 | return false; |
||
35 | } |
||
36 | |||
37 | 4 | return true; |
|
38 | } |
||
39 | |||
40 | 4 | private static function classAllowsNamedArguments(ClassReflection $classReflection): bool |
|
41 | { |
||
42 | 4 | $reflectionClass = $classReflection->getNativeReflection(); |
|
43 | |||
44 | 4 | while ($reflectionClass) { |
|
45 | 4 | if (self::hasNoNamedArgumentsTag($reflectionClass)) { |
|
46 | 1 | return false; |
|
47 | } |
||
48 | // Check if the class has @no-named-arguments annotation, even in the parent classes |
||
49 | 4 | $reflectionClass = $reflectionClass->getParentClass(); |
|
50 | } |
||
51 | |||
52 | 3 | if ($classReflection->isInterface()) { |
|
53 | // 🚨 Stop rule, cuz in runtime might be resolved any implementation of the interface, and the names of arguments might differ |
||
54 | return false; |
||
55 | } |
||
56 | |||
57 | 3 | return true; |
|
58 | } |
||
59 | |||
60 | 10 | private static function areArgumentsSuitable(array $args, array $parameters): bool |
|
61 | { |
||
62 | 10 | foreach ($args as $index => $arg) { |
|
63 | 10 | if (!isset($parameters[$index])) { |
|
64 | 2 | return false; |
|
65 | } |
||
66 | |||
67 | // Skip variadic parameters (...$param) |
||
68 | 8 | if ($parameters[$index]->isVariadic()) { |
|
69 | 1 | return false; |
|
70 | } |
||
71 | |||
72 | 8 | if ($arg instanceof Node\VariadicPlaceholder) { |
|
73 | return false; |
||
74 | } |
||
75 | |||
76 | // Skip unpacking arguments (...$var) |
||
77 | 8 | if ($arg instanceof Node\Arg && $arg->unpack) { |
|
78 | 1 | return false; |
|
79 | } |
||
80 | |||
81 | // skip already named arguments |
||
82 | 7 | if ($arg->name !== null) { |
|
83 | 1 | return false; |
|
84 | } |
||
85 | } |
||
86 | |||
87 | 5 | return true; |
|
88 | } |
||
89 | |||
90 | 5 | private static function hasNoNamedArgumentsTag(ReflectionFunctionAbstract|ReflectionClass|ReflectionEnum $reflection): bool |
|
91 | { |
||
92 | 5 | $docComment = $reflection->getDocComment(); |
|
93 | |||
94 | 5 | return $docComment !== false && str_contains(haystack: $docComment, needle: '@no-named-arguments'); |
|
95 | } |
||
96 | |||
97 | 4 | private static function functionAllowsNamedArguments( |
|
98 | FuncCall|StaticCall|MethodCall|New_ $node, |
||
99 | ?ClassReflection $classReflection = null, |
||
100 | ): bool { |
||
101 | 4 | $functionReflection = Reflection::getFunctionReflection(node: $node, classReflection: $classReflection); |
|
102 | 4 | if ($functionReflection === null) { |
|
103 | return false; // 🚨 Stop rule if method doesn't exist (likely a @method annotation) |
||
104 | } |
||
105 | |||
106 | 4 | if (self::hasNoNamedArgumentsTag($functionReflection)) { |
|
107 | return false; |
||
108 | } |
||
109 | |||
110 | 4 | return true; |
|
111 | } |
||
112 | } |
||
113 |