Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
NoBaseExceptionVisitor
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
90
0.00% covered (danger)
0.00%
0 / 1
 visitNew
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWikiPhanConfig\Plugin;
6
7use ast\Node;
8use Phan\AST\UnionTypeVisitor;
9use Phan\Exception\FQSENException;
10use Phan\Exception\IssueException;
11use Phan\Language\FQSEN\FullyQualifiedClassName;
12use Phan\PluginV3\PluginAwarePostAnalysisVisitor;
13use const ast\AST_NAME;
14
15class NoBaseExceptionVisitor extends PluginAwarePostAnalysisVisitor {
16    /**
17     * @inheritDoc
18     */
19    public function visitNew( Node $node ): void {
20        $classNode = $node->children['class'];
21        if ( !$classNode instanceof Node ) {
22            // Syntax error, bail out
23            return;
24        }
25        if ( $classNode->kind !== AST_NAME ) {
26            // A variable or another dynamic expression, skip for now.
27            // TODO We could try and handle this, at least in the easy cases (e.g., if the value of the variable
28            // can be inferred statically).
29            // Note that we can't just check the union type, because it could be `Exception` even if the class is not
30            // `Exception` itself (e.g., if the union type is inferred from a doc comment or typehint that only uses
31            // `Exception` as the ancestor type of all the potential values).
32            return;
33        }
34        if ( !isset( $classNode->children['name'] ) ) {
35            // Syntax error, bail out
36            return;
37        }
38
39        try {
40            $classNameType = UnionTypeVisitor::unionTypeFromClassNode(
41                $this->code_base,
42                $this->context,
43                $classNode
44            );
45        } catch ( IssueException | FQSENException $_ ) {
46            return;
47        }
48
49        if ( $classNameType->typeCount() !== 1 ) {
50            // Shouldn't happen
51            return;
52        }
53        $classType = $classNameType->getTypeSet()[0];
54        if ( !$classType->isObjectWithKnownFQSEN() ) {
55            // Syntax error or something.
56            return;
57        }
58        $classFQSEN = $classType->asFQSEN();
59        if ( !$classFQSEN instanceof FullyQualifiedClassName ) {
60            // Should not happen.
61            return;
62        }
63
64        $classFQSENStr = (string)$classFQSEN;
65        if ( $classFQSENStr === '\Exception' ) {
66            self::emitPluginIssue(
67                $this->code_base,
68                $this->context,
69                NoBaseExceptionPlugin::ISSUE_TYPE,
70                // Links to https://www.mediawiki.org/wiki/Manual:Coding_conventions/PHP#Exception_handling
71                'Instantiating {CLASS} directly is not allowed. Use SPL exceptions if the exception is ' .
72                    'unchecked, or define a custom exception class otherwise. See https://w.wiki/6nur',
73                [ $classFQSENStr ]
74            );
75        }
76    }
77}