Passed
Push — master ( 646450...8b10c9 )
by Kyle
49s queued 11s
created

AbstractLocalVariable::getNode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of PHP Mess Detector.
4
 *
5
 * Copyright (c) Manuel Pichler <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * Licensed under BSD License
9
 * For full copyright and license information, please see the LICENSE file.
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @author Manuel Pichler <[email protected]>
13
 * @copyright Manuel Pichler. All rights reserved.
14
 * @license https://opensource.org/licenses/bsd-license.php BSD License
15
 * @link http://phpmd.org/
16
 */
17
18
namespace PHPMD\Rule;
19
20
use PDepend\Source\AST\ASTArrayIndexExpression;
21
use PDepend\Source\AST\ASTMemberPrimaryPrefix;
22
use PDepend\Source\AST\ASTPropertyPostfix;
23
use PDepend\Source\AST\ASTVariable;
24
use PDepend\Source\AST\ASTVariableDeclarator;
25
use PHPMD\AbstractNode;
26
use PHPMD\AbstractRule;
27
use PHPMD\Node\ASTNode;
28
29
/**
30
 * Base class for rules that rely on local variables.
31
 *
32
 * @since 0.2.6
33
 */
34
abstract class AbstractLocalVariable extends AbstractRule
35
{
36
    /**
37
     * @var array Self reference class names.
38
     */
39
    protected $selfReferences = array('self', 'static');
40
41
    /**
42
     * PHP super globals that are available in all php scopes, so that they
43
     * can never be unused local variables.
44
     *
45
     * @var array(string=>boolean)
46
     * @link http://php.net/manual/en/reserved.variables.php
47
     */
48
    private static $superGlobals = array(
49
        '$argc' => true,
50
        '$argv' => true,
51
        '$_COOKIE' => true,
52
        '$_ENV' => true,
53
        '$_FILES' => true,
54
        '$_GET' => true,
55
        '$_POST' => true,
56
        '$_REQUEST' => true,
57
        '$_SERVER' => true,
58
        '$_SESSION' => true,
59
        '$GLOBALS' => true,
60
        '$HTTP_RAW_POST_DATA' => true,
61
        '$php_errormsg' => true,
62 43
        '$http_response_header' => true,
63
    );
64 43
65 43
    /**
66 43
     * Tests if the given variable node represents a local variable or if it is
67
     * a static object property or something similar.
68
     *
69
     * @param \PHPMD\Node\ASTNode $variable The variable to check.
70
     * @return boolean
71
     */
72
    protected function isLocal(ASTNode $variable)
73
    {
74
        return (false === $variable->isThis()
0 ignored issues
show
Documentation Bug introduced by
The method isThis does not exist on object<PHPMD\Node\ASTNode>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
75
            && $this->isNotSuperGlobal($variable)
76
            && $this->isRegularVariable($variable)
77 48
        );
78
    }
79 48
80
    /**
81
     * Tests if the given variable represents one of the PHP super globals
82
     * that are available in scopes.
83
     *
84
     * @param \PHPMD\AbstractNode $variable
85
     * @return boolean
86
     */
87
    protected function isNotSuperGlobal(AbstractNode $variable)
88
    {
89 43
        return !isset(self::$superGlobals[$variable->getImage()]);
90
    }
91 43
92 43
    /**
93
     * Tests if the given variable node is a regular variable an not property
94 43
     * or method postfix.
95 17
     *
96 17
     * @param \PHPMD\Node\ASTNode $variable
97 1
     * @return boolean
98
     */
99 16
    protected function isRegularVariable(ASTNode $variable)
100 16
    {
101
        $node = $this->stripWrappedIndexExpression($variable);
102
        $parent = $node->getParent();
103 39
104
        if ($parent->isInstanceOf('PropertyPostfix')) {
105
            $primaryPrefix = $parent->getParent();
106
            if ($primaryPrefix->getParent()->isInstanceOf('MemberPrimaryPrefix')) {
107
                return !$primaryPrefix->getParent()->isStatic();
108
            }
109
110
            return ($parent->getChild(0)->getNode() !== $node->getNode()
111
                || !$primaryPrefix->isStatic()
112
            );
113 43
        }
114
115 43
        return true;
116 43
    }
117
118
    /**
119 14
     * Removes all index expressions that are wrapped around the given node
120 14
     * instance.
121 14
     *
122
     * @param \PHPMD\Node\ASTNode $node
123 10
     * @return \PHPMD\Node\ASTNode
124
     */
125
    protected function stripWrappedIndexExpression(ASTNode $node)
126
    {
127
        if (false === $this->isWrappedByIndexExpression($node)) {
128
            return $node;
129
        }
130
131
        $parent = $node->getParent();
132 43
        if ($parent->getChild(0)->getNode() === $node->getNode()) {
133
            return $this->stripWrappedIndexExpression($parent);
0 ignored issues
show
Bug introduced by
It seems like $parent defined by $node->getParent() on line 131 can be null; however, PHPMD\Rule\AbstractLocal...rappedIndexExpression() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
134 43
        }
135 43
136
        return $node;
137
    }
138
139
    /**
140
     * Tests if the given variable node os part of an index expression.
141
     *
142
     * @param \PHPMD\Node\ASTNode $node
143
     * @return boolean
144
     */
145
    protected function isWrappedByIndexExpression(ASTNode $node)
146
    {
147 8
        return ($node->getParent()->isInstanceOf('ArrayIndexExpression')
148
            || $node->getParent()->isInstanceOf('StringIndexExpression')
149 8
        );
150
    }
151
152
    /**
153
     * PHP is case insensitive so we should compare function names case
154
     * insensitive.
155
     *
156
     * @param \PHPMD\AbstractNode $node
157
     * @param string $name
158
     * @return boolean
159
     */
160 12
    protected function isFunctionNameEqual(AbstractNode $node, $name)
161
    {
162 12
        return (0 === strcasecmp(trim($node->getImage(), '\\'), $name));
163
    }
164 12
165
    /**
166
     * AST puts namespace prefix to global functions called from a namespace.
167
     * This method checks if the last part of function fully qualified name is equal to $name
168
     *
169
     * @param \PHPMD\AbstractNode $node
170
     * @param string $name
171
     * @return boolean
172
     */
173
    protected function isFunctionNameEndingWith(AbstractNode $node, $name)
174
    {
175
        $parts = explode('\\', trim($node->getImage(), '\\'));
176
177
        return (0 === strcasecmp(array_pop($parts), $name));
178
    }
179
180
    /**
181
     * Get the image of the given variable node.
182
     *
183
     * Prefix self:: and static:: properties with "::".
184
     *
185
     * @param ASTVariable|ASTPropertyPostfix|ASTVariableDeclarator $variable
186
     * @return string
187
     */
188
    protected function getVariableImage($variable)
189
    {
190
        $image = $variable->getImage();
191
192
        if ($image === '::') {
193
            return $image.$variable->getChild(1)->getImage();
194
        }
195
196
        $base = $variable;
197
        $parent = $this->getNode($variable->getParent());
198
199
        while ($parent && $parent instanceof ASTArrayIndexExpression && $parent->getChild(0) === $base->getNode()) {
200
            $base = $parent;
201
            $parent = $this->getNode($base->getParent());
202
        }
203
204
        if ($parent && $parent instanceof ASTPropertyPostfix) {
205
            $parent = $parent->getParent();
206
207
            if ($parent instanceof ASTMemberPrimaryPrefix &&
208
                in_array($parent->getChild(0)->getImage(), $this->selfReferences)
209
            ) {
210
                return "::$image";
211
            }
212
        }
213
214
        return $image;
215
    }
216
217
    /**
218
     * Return the PDepend node of ASTNode PHPMD node.
219
     *
220
     * Or return the input as is if it's not an ASTNode PHPMD node.
221
     *
222
     * @param mixed $node
223
     * @return \PDepend\Source\AST\ASTArtifact|\PDepend\Source\AST\ASTNode
224
     */
225
    protected function getNode($node)
226
    {
227
        if ($node instanceof ASTNode) {
228
            return $node->getNode();
229
        }
230
231
        return $node;
232
    }
233
}
234