Passed
Push — master ( 62af2e...d65fca )
by Théo
07:27 queued 04:04
created

NodeTraverser::traverse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 7
ccs 4
cts 4
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;
16
17
use PhpParser\Node;
18
use PhpParser\Node\Name;
19
use PhpParser\Node\Stmt\Declare_;
20
use PhpParser\Node\Stmt\GroupUse;
21
use PhpParser\Node\Stmt\InlineHTML;
22
use PhpParser\Node\Stmt\Namespace_;
23
use PhpParser\Node\Stmt\Use_;
24
use PhpParser\Node\Stmt\UseUse;
25
use PhpParser\NodeTraverser as PhpParserNodeTraverser;
26
27
/**
28
 * @private
29
 */
30
final class NodeTraverser extends PhpParserNodeTraverser
31
{
32
    private $prefix;
33
34 371
    public function __construct(string $prefix)
35
    {
36 371
        parent::__construct();
37
38 371
        $this->prefix = $prefix;
39
    }
40
41
    /**
42
     * @inheritdoc
43
     */
44 371
    public function traverse(array $nodes)
45
    {
46 371
        $nodes = $this->wrapInNamespace($nodes);
47 371
        $nodes = $this->replaceGroupUseStatements($nodes);
48
49 371
        return parent::traverse($nodes);
50
    }
51
52
    /**
53
     * Wrap the statements in a namespace when necessary:.
54
     *
55
     * ```php
56
     * #!/usr/bin/env php
57
     * <?php declare(strict_types=1);
58
     *
59
     * // A small comment
60
     *
61
     * if (\true) {
62
     *  echo "yo";
63
     * }
64
     * ```
65
     *
66
     * Will result in:
67
     *
68
     * ```php
69
     * #!/usr/bin/env php
70
     * <?php declare(strict_types=1);
71
     *
72
     * // A small comment
73
     *
74
     * namespace {
75
     *     if (\true) {
76
     *      echo "yo";
77
     *     }
78
     * }
79
     * ```
80
     *
81
     * @param Node[] $nodes
82
     *
83
     * @return Node[]
84
     */
85 371
    private function wrapInNamespace(array $nodes): array
86
    {
87 371
        $realStatements = [];
88
89 371
        foreach ($nodes as $i => $node) {
90 370
            if ($node instanceof Declare_ || $node instanceof InlineHTML) {
91 7
                continue;
92
            }
93
94 368
            $firstRealStatementIndex = $i;
95 368
            $realStatements = array_slice($nodes, $i);
96
97 368
            break;
98
        }
99
100 371
        $firstRealStatement = current($realStatements);
101
102 371
        if (false !== $firstRealStatement && false === ($firstRealStatement instanceof Namespace_)) {
103 151
            $wrappedStatements = new Namespace_(null, $realStatements);
104
105 151
            array_splice($nodes, $firstRealStatementIndex, count($realStatements), [$wrappedStatements]);
0 ignored issues
show
Bug introduced by
The variable $firstRealStatementIndex does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
106
        }
107
108 371
        return $nodes;
109
    }
110
111
    /**
112
     * @param Node[] $nodes
113
     *
114
     * @return Node[]
115
     */
116 371
    private function replaceGroupUseStatements(array $nodes): array
117
    {
118 371
        foreach ($nodes as $node) {
119 370
            if (false === ($node instanceof Namespace_)) {
120 7
                continue;
121
            }
122
123
            /** @var Namespace_ $node */
124 368
            $statements = $node->stmts;
125
126 368
            $newStatements = [];
127
128 368
            foreach ($statements as $statement) {
129 355
                if ($statement instanceof GroupUse) {
130 6
                    $uses_ = $this->createUses_($statement);
131
132 6
                    array_splice($newStatements, count($newStatements), 0, $uses_);
133
                } else {
134 355
                    $newStatements[] = $statement;
135
                }
136
            }
137
138 368
            $node->stmts = $newStatements;
139
        }
140
141 371
        return $nodes;
142
    }
143
144
    /**
145
     * @param GroupUse $node
146
     *
147
     * @return Use_[]
148
     */
149 6
    private function createUses_(GroupUse $node): array
150
    {
151 6
        return array_map(
152 6
            function (UseUse $use) use ($node): Use_ {
153 6
                $newUse = new UseUse(
154 6
                    Name::concat($node->prefix, $use->name, $use->name->getAttributes()),
155 6
                    $use->alias,
156 6
                    $use->type,
157 6
                    $use->getAttributes()
158
                );
159
160 6
                return new Use_(
161 6
                    [$newUse],
162 6
                    $node->type,
163 6
                    $node->getAttributes()
164
                );
165 6
            },
166
            $node->uses
167
        );
168
    }
169
}
170