Passed
Push — master ( 8b4604...b089da )
by Théo
05:38 queued 03:31
created

FunctionIdentifierRecorder::enterNode()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 20
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the humbug/php-scoper package.
7
 *
8
 * Copyright (c) 2017 Théo FIDRY <[email protected]>,
9
 *                    Pádraic Brady <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Humbug\PhpScoper\PhpParser\NodeVisitor\NamespaceStmt;
16
17
use Humbug\PhpScoper\PhpParser\Node\FullyQualifiedFactory;
18
use Humbug\PhpScoper\PhpParser\NodeVisitor\ParentNodeAppender;
19
use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\IdentifierResolver;
20
use Humbug\PhpScoper\Symbol\EnrichedReflector;
21
use Humbug\PhpScoper\Symbol\SymbolsRegistry;
22
use PhpParser\Node;
23
use PhpParser\Node\Arg;
24
use PhpParser\Node\Expr\FuncCall;
25
use PhpParser\Node\Identifier;
26
use PhpParser\Node\Name;
27
use PhpParser\Node\Name\FullyQualified;
28
use PhpParser\Node\Scalar\String_;
29
use PhpParser\Node\Stmt\Function_;
30
use PhpParser\NodeVisitorAbstract;
31
32
/**
33
 * Records the user functions registered in the global namespace which have been whitelisted and whitelisted functions.
34
 *
35
 * @private
36
 */
37
final class FunctionIdentifierRecorder extends NodeVisitorAbstract
38
{
39
    private string $prefix;
40
    private IdentifierResolver $identifierResolver;
41
    private SymbolsRegistry $symbolsRegistry;
42
    private EnrichedReflector $enrichedReflector;
43
44
    public function __construct(
45
        string $prefix,
46
        IdentifierResolver $identifierResolver,
47
        SymbolsRegistry $symbolsRegistry,
48
        EnrichedReflector $enrichedReflector
49
    ) {
50
        $this->prefix = $prefix;
51
        $this->identifierResolver = $identifierResolver;
52
        $this->symbolsRegistry = $symbolsRegistry;
53
        $this->enrichedReflector = $enrichedReflector;
54
    }
55
56
    public function enterNode(Node $node): Node
57
    {
58
        if (!($node instanceof Identifier || $node instanceof Name || $node instanceof String_)
59
            || !ParentNodeAppender::hasParent($node)
60
        ) {
61
            return $node;
62
        }
63
64
        $resolvedName = $this->retrieveResolvedName($node);
65
66
        if (null !== $resolvedName
67
            && $this->enrichedReflector->isExposedFunction($resolvedName->toString())
68
        ) {
69
            $this->symbolsRegistry->recordFunction(
70
                $resolvedName,
71
                FullyQualifiedFactory::concat($this->prefix, $resolvedName),
72
            );
73
        }
74
75
        return $node;
76
    }
77
78
    private function retrieveResolvedName(Node $node): ?FullyQualified
79
    {
80
        if ($node instanceof Identifier) {
81
            return $this->retrieveResolvedNameForIdentifier($node);
82
        }
83
84
        if ($node instanceof Name) {
85
            return $this->retrieveResolvedNameForFuncCall($node);
86
        }
87
88
        if ($node instanceof String_) {
89
            return $this->retrieveResolvedNameForString($node);
90
        }
91
92
        return null;
93
    }
94
95
    private function retrieveResolvedNameForIdentifier(Identifier $identifier): ?FullyQualified
96
    {
97
        $parent = ParentNodeAppender::getParent($identifier);
98
99
        if (!($parent instanceof Function_)
100
            || $identifier === $parent->returnType
101
        ) {
102
            return null;
103
        }
104
105
        $resolvedName = $this->identifierResolver->resolveIdentifier($identifier);
106
107
        return $resolvedName instanceof FullyQualified ? $resolvedName : null;
108
    }
109
110
    private function retrieveResolvedNameForFuncCall(Name $name): ?FullyQualified
111
    {
112
        $parent = ParentNodeAppender::getParent($name);
113
114
        if (!($parent instanceof FuncCall)) {
115
            return null;
116
        }
117
118
        return $name instanceof FullyQualified ? $name : null;
119
    }
120
121
    private function retrieveResolvedNameForString(String_ $string): ?FullyQualified
122
    {
123
        $stringParent = ParentNodeAppender::getParent($string);
124
125
        if (!($stringParent instanceof Arg)) {
126
            return null;
127
        }
128
129
        $argParent = ParentNodeAppender::getParent($stringParent);
130
131
        if (!($argParent instanceof FuncCall)) {
132
            return null;
133
        }
134
135
        if (!self::isFunctionExistsCall($argParent)) {
136
            return null;
137
        }
138
139
        $resolvedName = $this->identifierResolver->resolveString($string);
140
141
        return $resolvedName instanceof FullyQualified ? $resolvedName : null;
0 ignored issues
show
introduced by
$resolvedName is always a sub-type of PhpParser\Node\Name\FullyQualified.
Loading history...
142
    }
143
144
    private static function isFunctionExistsCall(FuncCall $node): bool
145
    {
146
        $name = $node->name;
147
148
        return $name instanceof Name
149
            && $name->isFullyQualified()
150
            && $name->toString() === 'function_exists';
151
    }
152
}
153