Completed
Push — master ( a44080...243d41 )
by Théo
04:58 queued 02:34
created

StringScalarPrefixer::prefixStringScalar()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 3
nop 1
dl 0
loc 22
ccs 10
cts 11
cp 0.9091
crap 4.0119
rs 8.9197
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\NodeVisitor;
16
17
use Humbug\PhpScoper\Reflector;
18
use PhpParser\Node;
19
use PhpParser\Node\Arg;
20
use PhpParser\Node\Const_;
21
use PhpParser\Node\Expr\ArrayItem;
22
use PhpParser\Node\Expr\Assign;
23
use PhpParser\Node\Expr\FuncCall;
24
use PhpParser\Node\Name;
25
use PhpParser\Node\Name\FullyQualified;
26
use PhpParser\Node\Param;
27
use PhpParser\Node\Scalar\String_;
28
use PhpParser\Node\Stmt\PropertyProperty;
29
use PhpParser\NodeVisitorAbstract;
30
use function is_string;
31
use function preg_match;
32
33
/**
34
 * Prefixes the string scalar values.
35
 *
36
 * ```
37
 * $x = 'Foo\Bar';
38
 * ```
39
 *
40
 * =>
41
 *
42
 * ```
43
 * $x = 'Humbug\Foo\Bar';
44
 * ```
45
 */
46
final class StringScalarPrefixer extends NodeVisitorAbstract
47
{
48
    private $prefix;
49
    private $whitelistedFunctions;
50
    private $whitelist;
51
    private $reflector;
52
53
    /**
54
     * @param string    $prefix
55
     * @param string[]  $whitelistedFunctions
56
     * @param string[]  $whitelist
57
     * @param Reflector $reflector
58
     */
59 353
    public function __construct(
60
        string $prefix,
61
        array $whitelistedFunctions,
62
        array $whitelist,
63
        Reflector $reflector
64
    ) {
65 353
        $this->prefix = $prefix;
66 353
        $this->whitelistedFunctions = $whitelistedFunctions;
67 353
        $this->whitelist = $whitelist;
68 353
        $this->reflector = $reflector;
69
    }
70
71
    /**
72
     * @inheritdoc
73
     */
74 352
    public function enterNode(Node $node): Node
75
    {
76 352
        return ($this->shouldPrefixScalar($node))
77 11
            ? $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...
78 352
            : $node
79
        ;
80
    }
81
82 352
    private function shouldPrefixScalar(Node $node): bool
83
    {
84 352
        if (false === ($node instanceof String_ && AppendParentNode::hasParent($node) && is_string($node->value))
85 352
            || 1 !== preg_match('/^\\\\*(?:\p{L}+\\\\+)++\p{L}+$/', $node->value)
86
        ) {
87 352
            return false;
88
        }
89
        /** @var String_ $node */
90 13
        $parentNode = AppendParentNode::getParent($node);
91
92 13
        if ($parentNode instanceof Arg
93 13
            && null !== $funcNode = AppendParentNode::findParent($parentNode)
94
        ) {
95 2
            $funcNode = AppendParentNode::getParent($parentNode);
96
97
            return
98 2
                $funcNode instanceof FuncCall
99 2
                && $funcNode->name instanceof Name
100 2
                && false === $funcNode->hasAttribute('whitelist_class_alias')
101
            ;
102
        }
103
104 11
        return $parentNode instanceof Assign
105 11
            || $parentNode instanceof ArrayItem
106 11
            || $parentNode instanceof Param
107 11
            || $parentNode instanceof Const_
108 11
            || $parentNode instanceof PropertyProperty
109
        ;
110
    }
111
112 11
    private function prefixStringScalar(String_ $string): Node
113
    {
114 11
        $stringName = new Name(
115 11
            preg_replace('/^\\\\(.+)$/', '$1', $string->value),
116 11
            $string->getAttributes()
117
        );
118
119
        // Skip if is already prefixed
120 11
        if ($this->prefix === $stringName->getFirst()) {
121 10
            $newStringName = $stringName;
122
        // Check if the class can be prefixed: class from the global namespace
123
        } elseif (
124 11
            $this->reflector->isClassInternal($stringName->toString())
125 11
            || 1 === count($stringName->parts)
126
        ) {
127
            $newStringName = $stringName;
128
        } else {
129 11
            $newStringName = FullyQualified::concat($this->prefix, $stringName->toString(), $stringName->getAttributes());
130
        }
131
132 11
        return new String_($newStringName->toString(), $string->getAttributes());
133
    }
134
}
135