Test Setup Failed
Push — master ( 38b715...21bd50 )
by Théo
02:18
created

StringScalarPrefixer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 3
dl 0
loc 6
ccs 2
cts 2
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
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;
16
17
use Humbug\PhpScoper\Reflector;
18
use Humbug\PhpScoper\Whitelist;
19
use PhpParser\Node;
20
use PhpParser\Node\Arg;
21
use PhpParser\Node\Const_;
22
use PhpParser\Node\Expr\ArrayItem;
23
use PhpParser\Node\Expr\Assign;
24
use PhpParser\Node\Expr\FuncCall;
25
use PhpParser\Node\Expr\MethodCall;
26
use PhpParser\Node\Expr\StaticCall;
27
use PhpParser\Node\Name;
28
use PhpParser\Node\Name\FullyQualified;
29
use PhpParser\Node\Param;
30
use PhpParser\Node\Scalar\String_;
31
use PhpParser\Node\Stmt\PropertyProperty;
32
use PhpParser\NodeVisitorAbstract;
33
use function is_string;
34
use function preg_match;
35
36
/**
37
 * Prefixes the string scalar values.
38
 *
39
 * ```
40
 * $x = 'Foo\Bar';
41
 * ```
42
 *
43
 * =>
44
 *
45
 * ```
46
 * $x = 'Humbug\Foo\Bar';
47
 * ```
48
 *
49
 * @private
50
 */
51
final class StringScalarPrefixer extends NodeVisitorAbstract
52
{
53
    private $prefix;
54
    private $whitelist;
55 422
    private $reflector;
56
57 422
    public function __construct(string $prefix, Whitelist $whitelist, Reflector $reflector)
58 422
    {
59
        $this->prefix = $prefix;
60
        $this->whitelist = $whitelist;
61
        $this->reflector = $reflector;
62
    }
63
64 421
    /**
65
     * @inheritdoc
66 421
     */
67 12
    public function enterNode(Node $node): Node
68 421
    {
69
        return ($this->shouldPrefixScalar($node))
70
            ? $this->prefixStringScalar($node)
0 ignored issues
show
Compatibility introduced by
$node of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Scalar\String_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
71
            : $node
72 421
        ;
73
    }
74 421
75 421
    private function shouldPrefixScalar(Node $node): bool
76
    {
77 421
        if (false === ($node instanceof String_ && AppendParentNode::hasParent($node) && is_string($node->value))
78
            || 1 !== preg_match('/^\\\\*(?:[\p{L}_]+\\\\+)++[\p{L}_]+$/u', $node->value)
79
        ) {
80 14
            return false;
81
        }
82 14
        /** @var String_ $node */
83 14
        $parentNode = AppendParentNode::getParent($node);
84
85 4
        if ($parentNode instanceof Arg
86
            && null !== $funcNode = AppendParentNode::findParent($parentNode)
87 4
        ) {
88 2
            $funcNode = AppendParentNode::getParent($parentNode);
89
90
            if ($funcNode instanceof FuncCall) {
91 2
                return $funcNode->name instanceof Name && false === $funcNode->hasAttribute('whitelist_class_alias');
92
            }
93
94 12
            return $funcNode instanceof MethodCall || $funcNode instanceof StaticCall;
95 12
        }
96 12
97 12
        return $parentNode instanceof Assign
98 12
            || $parentNode instanceof ArrayItem
99
            || $parentNode instanceof Param
100
            || $parentNode instanceof Const_
101
            || $parentNode instanceof PropertyProperty
102 12
        ;
103
    }
104 12
105 12
    private function prefixStringScalar(String_ $string): Node
106 12
    {
107
        $stringName = new Name(
108
            preg_replace('/^\\\\(.+)$/', '$1', $string->value),
109
            $string->getAttributes()
110 12
        );
111 10
112
        $isConstantNode = $this->isConstantNode($string);
113
114 12
        // Skip if is already prefixed
115 12
        if ($this->prefix === $stringName->getFirst()) {
116
            $newStringName = $stringName;
117
        // Check if the class can be prefixed: class not from the global namespace or which the namespace is not
118
        // whitelisted
119 12
        } elseif (
120
            1 === count($stringName->parts)
121
            || $this->reflector->isClassInternal($stringName->toString())
122 12
            || (false === $isConstantNode && $this->whitelist->isClassWhitelisted((string) $stringName))
123
            || ($isConstantNode && $this->whitelist->isConstantWhitelisted((string) $stringName))
124
            || $this->whitelist->isNamespaceWhitelisted((string) $stringName)
125
        ) {
126
            $newStringName = $stringName;
127
        } else {
128
            $newStringName = FullyQualified::concat($this->prefix, $stringName->toString(), $stringName->getAttributes());
129
        }
130
131
        return new String_($newStringName->toString(), $string->getAttributes());
132
    }
133
134
    private function isConstantNode(String_ $node): bool
135
    {
136
        $parent = AppendParentNode::getParent($node);
137
138
        if (false === ($parent instanceof Arg)) {
139
            return false;
140
        }
141
142
        /** @var Arg $parent */
143
        $argParent = AppendParentNode::getParent($parent);
144
145
        if (false === ($argParent instanceof FuncCall)) {
146
            return false;
147
        }
148
149
        /* @var FuncCall $argParent */
150
        return 'define' === (string) $argParent->name;
151
    }
152
}
153