Passed
Push — main ( aa1c08...38dda7 )
by mikhail
13:28
created

MethodAnalyzer::methodNeedsGeneric()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 10
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SavinMikhail\CommentsDensity\MissingDocblock;
6
7
use ArrayAccess;
8
use Iterator;
9
use PhpParser\Node;
10
use PhpParser\Node\ComplexType;
11
use PhpParser\Node\Identifier;
12
use PhpParser\Node\Name;
13
use PhpParser\Node\Stmt\ClassMethod;
14
use PhpParser\Node\Stmt\Function_;
15
use PhpParser\NodeTraverser;
16
use ReflectionClass;
17
use Traversable;
18
19
use function class_exists;
20
use function in_array;
21
use function interface_exists;
22
23
final class MethodAnalyzer
24
{
25 34
    public function methodNeedsGeneric(ClassMethod|Function_ $node): bool
26
    {
27 34
        $returnType = $node->getReturnType();
28
29 34
        if ($this->isTypeIterable($returnType)) {
30 15
            return true;
31
        }
32
33 19
        foreach ($node->getParams() as $param) {
34 12
            if ($this->isTypeIterable($param->type)) {
35 8
                return true;
36
            }
37
        }
38
39 11
        return false;
40
    }
41
42 34
    private function isTypeIterable(ComplexType|Identifier|Name|null $type): bool
43
    {
44 34
        if ($type === null) {
45 2
            return false;
46
        }
47
48
        if (
49 34
            $type instanceof Identifier
50 34
            && in_array($type->toString(), ['array', 'iterable'], true)
51
        ) {
52 10
            return true;
53
        }
54
55
        if (
56 26
            $type instanceof Name
57 26
            && in_array($type->toString(), ['Generator', 'Traversable', 'Iterator', 'ArrayAccess'], true)
58
        ) {
59 8
            return true;
60
        }
61
62 22
        return $this->isClassOrInterfaceTraversable($type);
63
    }
64
65 22
    private function isClassOrInterfaceTraversable(ComplexType|Identifier|Name $type): bool
66
    {
67 22
        if (!($type instanceof Name)) {
68 16
            return false;
69
        }
70
71 8
        $typeName = $type->toString();
72
73 8
        if (!class_exists($typeName) && !interface_exists($typeName)) {
74 3
            return false;
75
        }
76
77 5
        return $this->isTraversableRecursively(new ReflectionClass($typeName));
78
    }
79
80 5
    private function isTraversableRecursively(ReflectionClass $reflection): bool
81
    {
82 5
        if ($reflection->implementsInterface(Iterator::class)
83 2
            || $reflection->implementsInterface(ArrayAccess::class)
84 5
            || $reflection->implementsInterface(Traversable::class)) {
85 5
            return true;
86
        }
87
88
        foreach ($reflection->getInterfaces() as $interface) {
89
            if ($this->isTraversableRecursively($interface)) {
90
                return true;
91
            }
92
        }
93
94
        $parentClass = $reflection->getParentClass();
95
        if ($parentClass !== false) {
96
            return $this->isTraversableRecursively($parentClass);
97
        }
98
99
        return false;
100
    }
101
102 34
    public function methodThrowsUncaughtExceptions(Node $node): bool
103
    {
104 34
        $traverser = new NodeTraverser();
105 34
        $visitor = new UncaughtExceptionVisitor();
106
107 34
        $traverser->addVisitor($visitor);
108 34
        $traverser->traverse([$node]);
109
110 34
        return $visitor->hasUncaughtThrows;
111
    }
112
}
113