Completed
Pull Request — master (#55)
by
unknown
01:16
created

MustRethrowRule.php$0 ➔ isProbablyAThrow()   A

Complexity

Conditions 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 3
1
<?php
2
3
4
namespace TheCodingMachine\PHPStan\Rules\Exceptions;
5
6
use Exception;
7
use function in_array;
8
use PhpParser\Node;
9
use PhpParser\NodeFinder;
10
use PhpParser\Node\Stmt\Catch_;
11
use PhpParser\NodeTraverser;
12
use PhpParser\NodeVisitorAbstract;
13
use PhpParser\Node\Expr\Variable;
14
use PhpParser\Node\Expr\StaticCall;
15
use PHPStan\Analyser\Scope;
16
use PHPStan\Broker\Broker;
17
use PHPStan\Rules\Rule;
18
use RuntimeException;
19
use TheCodingMachine\PHPStan\Utils\PrefixGenerator;
20
use Throwable;
21
22
/**
23
 * When catching \Exception, \RuntimeException or \Throwable, the exception MUST be thrown again
24
 * (unless you are developing an exception handler...)
25
 *
26
 * @implements Rule<Catch_>
27
 */
28
class MustRethrowRule implements Rule
29
{
30
    public function getNodeType(): string
31
    {
32
        return Catch_::class;
33
    }
34
35
    /**
36
     * @param Catch_ $node
37
     * @param \PHPStan\Analyser\Scope $scope
38
     * @return string[]
39
     */
40
    public function processNode(Node $node, Scope $scope): array
41
    {
42
        // Let's only apply the filter to \Exception, \RuntimeException or \Throwable
43
        $exceptionType = null;
44
        foreach ($node->types as $type) {
0 ignored issues
show
Bug introduced by
Accessing types on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
45
            if (in_array((string)$type, [Exception::class, RuntimeException::class, Throwable::class], true)) {
46
                $exceptionType = (string)$type;
47
                break;
48
            }
49
        }
50
51
        if ($exceptionType === null) {
52
            return [];
53
        }
54
55
        $exceptionVarName = $node->var->name;
0 ignored issues
show
Bug introduced by
Accessing var on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
56
57
        // Let's visit and find a throw.
58
        $visitor = new class($exceptionVarName)  extends NodeVisitorAbstract {
59
            /**
60
             * @var bool
61
             */
62
            private $throwFound = false;
63
64
            /**
65
             * @var bool
66
             */
67
            private $throwFoundProbably = false;
68
69
            private $exceptionVarName;
70
71
            public function __construct(string $exceptionVarName)
72
            {
73
                $this->exceptionVarName = $exceptionVarName;
74
            }
75
76
            public function leaveNode(Node $node)
77
            {
78
                // Only rethrow through static methods are allowed
79
                if ($node instanceof StaticCall) {
80
                    $this->throwFoundProbably = $this->isProbablyAThrow($node);
81
                }
82
83
                if ($node instanceof Node\Stmt\Throw_) {
84
                    $this->throwFound = true;
85
                }
86
                return null;
87
            }
88
89
            /**
90
             * @return bool
91
             */
92
            public function isThrowFound(): bool
93
            {
94
                return $this->throwFound;
95
            }
96
97
            /**
98
             * @return bool
99
             */
100
            public function isThrowProbablyFound(): bool
101
            {
102
                return $this->throwFoundProbably;
103
            }
104
105
            private function isProbablyAThrow(Node $node)
106
            {
107
                if (!$args = $node->args) {
0 ignored issues
show
Bug introduced by
Accessing args on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
108
                    return false;
109
                }
110
111
                $varArgs = array_filter($args, function ($arg) {
112
                    return $arg->value instanceof Variable && $arg->value->name === $this->exceptionVarName ;
113
                });
114
115
                return 0 !== count($varArgs);
116
            }
117
        };
118
119
        $traverser = new NodeTraverser();
120
121
        $traverser->addVisitor($visitor);
122
123
        $traverser->traverse($node->stmts);
0 ignored issues
show
Bug introduced by
Accessing stmts on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
124
125
        $errors = [];
126
127
        if (!$visitor->isThrowFound() && !$visitor->isThrowProbablyFound()) {
128
            $errors[] = sprintf('%scaught "%s" must be rethrown. Either catch a more specific exception or add a "throw" clause in the "catch" block to propagate the exception. More info: http://bit.ly/failloud', PrefixGenerator::generatePrefix($scope), $exceptionType);
129
        }
130
131
        return $errors;
132
    }
133
}
134