Passed
Push — main ( 5832e5...2c3933 )
by mikhail
03:16
created

UncaughtExceptionVisitor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 3
c 2
b 0
f 0
dl 0
loc 5
ccs 0
cts 4
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SavinMikhail\CommentsDensity\MissingDocblock\Visitors;
6
7
use phpDocumentor\Reflection\DocBlockFactory;
8
use PhpParser\Node;
9
use PhpParser\Node\Expr\MethodCall;
10
use PhpParser\Node\Expr\Throw_;
11
use PhpParser\Node\Expr\Variable;
12
use PhpParser\Node\Stmt\Class_;
13
use PhpParser\Node\Stmt\ClassMethod;
14
use PhpParser\Node\Stmt\TryCatch;
15
use PhpParser\NodeVisitorAbstract;
16
use ReflectionClass;
17
18
final class UncaughtExceptionVisitor extends NodeVisitorAbstract
19
{
20
    private MethodRegistrar $methodRegistrar;
21
    private ExceptionChecker $exceptionChecker;
22
    private DocBlockFactory $docBlockFactory;
23
24
    public function __construct(?Class_ $class)
25
    {
26
        $this->methodRegistrar = new MethodRegistrar($class);
27
        $this->exceptionChecker = new ExceptionChecker();
28
        $this->docBlockFactory = DocBlockFactory::createInstance();
29
    }
30
31
    public function hasUncaughtException(): bool
32
    {
33
        return $this->exceptionChecker->hasUncaughtThrows;
34
    }
35
36
    public function enterNode(Node $node): ?Node
37
    {
38
        if ($node instanceof ClassMethod) {
39
            $this->methodRegistrar->registerClassMethod($node);
40
        }
41
42
        if ($node instanceof TryCatch) {
43
            $this->exceptionChecker->pushTryCatch($node);
44
        }
45
46
        if ($node instanceof Throw_) {
47
            $this->exceptionChecker->checkIfExceptionIsCaught($node);
48
        }
49
50
        if ($node instanceof MethodCall) {
51
            $this->checkMethodCallForThrowingUncaughtException($node);
52
        }
53
54
        return null;
55
    }
56
57
    public function leaveNode(Node $node): ?Node
58
    {
59
        if ($node instanceof TryCatch) {
60
            $this->exceptionChecker->popTryCatch();
61
        }
62
63
        return null;
64
    }
65
66
    private function checkMethodCallForThrowingUncaughtException(MethodCall $node): void
67
    {
68
        $methodName = $node->name->name;
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
69
        if (!isset($node->var->name)) {
70
            return;
71
        }
72
73
        $objectName = (string) $node->var->name;
74
        $class = $this->methodRegistrar->getVariableTypes()[$objectName] ?? null;
75
76
        if (!$class) {
77
            return;
78
        }
79
80
        $exceptions = $this->getMethodThrownExceptions($class, $methodName);
81
        foreach ($exceptions as $exception) {
82
            $throwNode = new Throw_(new Variable($exception), $node->getAttributes());
83
            $this->exceptionChecker->checkIfExceptionIsCaught($throwNode);
84
        }
85
    }
86
87
    private function getMethodThrownExceptions(string $className, string $methodName): array
88
    {
89
        if (!class_exists($className)) {
90
            return [];
91
        }
92
93
        $reflectionClass = new ReflectionClass($className);
94
        if (!$reflectionClass->hasMethod($methodName)) {
95
            return [];
96
        }
97
98
        $reflectionMethod = $reflectionClass->getMethod($methodName);
99
        $docComment = $reflectionMethod->getDocComment();
100
101
        if (!$docComment) {
102
            return [];
103
        }
104
105
        $docBlock = $this->docBlockFactory->create($docComment);
106
107
        $exceptions = [];
108
        foreach ($docBlock->getTagsByName('throws') as $tag) {
109
            $exceptions[] = (string)$tag->getType();
110
        }
111
112
        return $exceptions;
113
    }
114
}
115