Issues (12)

src/Config/DefaultStrategy.php (1 issue)

Labels
Severity
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
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 18 at column 6
Loading history...
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