Passed
Push — feature/issue-714-fix-self-and... ( 38aaf5...5ed0d1 )
by Kyle
01:55
created

UndefinedVariable   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 260
Duplicated Lines 13.08 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
dl 34
loc 260
rs 8.96
c 0
b 0
f 0
wmc 43
lcom 1
cbo 6

13 Methods

Rating   Name   Duplication   Size   Complexity  
B apply() 0 21 6
A collect() 0 11 1
A collectGlobalStatements() 0 10 3
A collectExceptionCatches() 12 12 4
A collectListExpressions() 10 10 3
B collectForeachStatements() 0 18 7
A collectClosureParameters() 0 8 2
A checkVariableDefined() 0 6 2
A collectParameters() 0 12 2
A collectAssignments() 0 16 4
A collectPropertyPostfix() 12 12 4
A addVariableDefinition() 0 8 2
A isNameAllowedInContext() 0 8 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like UndefinedVariable often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UndefinedVariable, and based on these observations, apply Extract Interface, too.

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\CleanCode;
19
20
use PDepend\Source\AST\ASTArray;
21
use PDepend\Source\AST\ASTPropertyPostfix;
22
use PDepend\Source\AST\ASTUnaryExpression;
23
use PDepend\Source\AST\ASTVariable;
24
use PDepend\Source\AST\ASTVariableDeclarator;
25
use PDepend\Source\AST\State;
26
use PHPMD\AbstractNode;
27
use PHPMD\Node\AbstractCallableNode;
28
use PHPMD\Node\ASTNode;
29
use PHPMD\Node\MethodNode;
30
use PHPMD\Rule\AbstractLocalVariable;
31
use PHPMD\Rule\FunctionAware;
32
use PHPMD\Rule\MethodAware;
33
34
/**
35
 * This rule collects all undefined variables within a given function or method
36
 * that are used by any code in the analyzed source artifact.
37
 */
38
class UndefinedVariable extends AbstractLocalVariable implements FunctionAware, MethodAware
39
{
40
    /**
41
     * @var array Self reference class names.
42
     */
43
    protected $selfReferences = array('self', 'static');
44
45
    /**
46
     * Found variable images within a single method or function.
47
     *
48
     * @var array(string)
49
     */
50
    private $images = array();
51
52
    /**
53
     * This method checks that all local variables within the given function or
54
     * method are used at least one time.
55
     *
56
     * @param \PHPMD\AbstractNode $node
57
     * @return void
58
     */
59
    public function apply(AbstractNode $node)
60
    {
61
        $this->images = array();
62
63
        $this->collect($node);
64
65
        foreach ($node->findChildrenOfType('Class') as $class) {
66
            foreach ($class->getMethods() as $method) {
0 ignored issues
show
Documentation Bug introduced by
The method getMethods 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...
67
                $this->collect(new MethodNode($method));
68
            }
69
        }
70
71
        foreach ($node->findChildrenOfType('Variable') as $variable) {
72
            if (!$this->isNotSuperGlobal($variable)) {
73
                $this->addVariableDefinition($variable);
0 ignored issues
show
Documentation introduced by
$variable is of type object<PHPMD\Node\ASTNode>, but the function expects a object<PDepend\Source\AS...\ASTVariableDeclarator>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
74
            }
75
            if (!$this->checkVariableDefined($variable, $node)) {
0 ignored issues
show
Compatibility introduced by
$node of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\AbstractCallableNode>. It seems like you assume a child class of the class PHPMD\AbstractNode 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...
76
                $this->addViolation($variable, array($this->getVariableImage($variable)));
0 ignored issues
show
Documentation introduced by
$variable is of type object<PHPMD\Node\ASTNode>, but the function expects a object<PDepend\Source\AS...\ASTVariableDeclarator>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
77
            }
78
        }
79
    }
80
81
    /**
82
     * Collect variables defined inside a PHPMD entry node (such as MethodNode).
83
     *
84
     * @param AbstractNode $node
85
     */
86
    private function collect(AbstractNode $node)
87
    {
88
        $this->collectPropertyPostfix($node);
89
        $this->collectClosureParameters($node);
0 ignored issues
show
Compatibility introduced by
$node of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\AbstractCallableNode>. It seems like you assume a child class of the class PHPMD\AbstractNode 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...
90
        $this->collectForeachStatements($node);
0 ignored issues
show
Compatibility introduced by
$node of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\AbstractCallableNode>. It seems like you assume a child class of the class PHPMD\AbstractNode 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...
91
        $this->collectListExpressions($node);
0 ignored issues
show
Compatibility introduced by
$node of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\AbstractCallableNode>. It seems like you assume a child class of the class PHPMD\AbstractNode 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...
92
        $this->collectAssignments($node);
0 ignored issues
show
Compatibility introduced by
$node of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\AbstractCallableNode>. It seems like you assume a child class of the class PHPMD\AbstractNode 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...
93
        $this->collectParameters($node);
94
        $this->collectExceptionCatches($node);
0 ignored issues
show
Compatibility introduced by
$node of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\AbstractCallableNode>. It seems like you assume a child class of the class PHPMD\AbstractNode 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...
95
        $this->collectGlobalStatements($node);
96
    }
97
98
    /**
99
     * Stores the given literal node in an global of found variables.
100
     *
101
     * @param \PHPMD\Node\AbstractNode $node
102
     * @return void
103
     */
104
    private function collectGlobalStatements(AbstractNode $node)
105
    {
106
        $globalStatements = $node->findChildrenOfType('GlobalStatement');
107
108
        foreach ($globalStatements as $globalStatement) {
109
            foreach ($globalStatement->getChildren() as $variable) {
0 ignored issues
show
Documentation Bug introduced by
The method getChildren 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...
110
                $this->addVariableDefinition($variable);
111
            }
112
        }
113
    }
114
115
    /**
116
     * Stores the given literal node in an catch of found variables.
117
     *
118
     * @param \PHPMD\Node\AbstractCallableNode $node
119
     * @return void
120
     */
121 View Code Duplication
    private function collectExceptionCatches(AbstractCallableNode $node)
122
    {
123
        $catchStatements = $node->findChildrenOfType('CatchStatement');
124
125
        foreach ($catchStatements as $catchStatement) {
126
            foreach ($catchStatement->getChildren() as $children) {
0 ignored issues
show
Documentation Bug introduced by
The method getChildren 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...
127
                if ($children instanceof ASTVariable) {
128
                    $this->addVariableDefinition($children);
129
                }
130
            }
131
        }
132
    }
133
134
    /**
135
     * Stores the given literal node in an internal list of found variables.
136
     *
137
     * @param \PHPMD\Node\AbstractCallableNode $node
138
     * @return void
139
     */
140 View Code Duplication
    private function collectListExpressions(AbstractCallableNode $node)
141
    {
142
        $lists = $node->findChildrenOfType('ListExpression');
143
144
        foreach ($lists as $listExpression) {
145
            foreach ($listExpression->getChildren() as $variable) {
0 ignored issues
show
Documentation Bug introduced by
The method getChildren 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...
146
                $this->addVariableDefinition($variable);
147
            }
148
        }
149
    }
150
151
    /**
152
     * Stores the given literal node in an internal foreach of found variables.
153
     *
154
     * @param \PHPMD\Node\AbstractCallableNode $node
155
     * @return void
156
     */
157
    private function collectForeachStatements(AbstractCallableNode $node)
158
    {
159
        $foreachStatements = $node->findChildrenOfType('ForeachStatement');
160
161
        foreach ($foreachStatements as $foreachStatement) {
162
            foreach ($foreachStatement->getChildren() as $children) {
0 ignored issues
show
Documentation Bug introduced by
The method getChildren 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...
163
                if ($children instanceof ASTVariable) {
164
                    $this->addVariableDefinition($children);
165
                } elseif ($children instanceof ASTUnaryExpression) {
166
                    foreach ($children->getChildren() as $refChildren) {
167
                        if ($refChildren instanceof ASTVariable) {
168
                            $this->addVariableDefinition($refChildren);
169
                        }
170
                    }
171
                }
172
            }
173
        }
174
    }
175
176
    /**
177
     * Stores the given literal node in an internal closure of found variables.
178
     *
179
     * @param \PHPMD\Node\AbstractCallableNode $node
180
     * @return void
181
     */
182
    private function collectClosureParameters(AbstractCallableNode $node)
183
    {
184
        $closures = $node->findChildrenOfType('Closure');
185
186
        foreach ($closures as $closure) {
187
            $this->collectParameters($closure);
188
        }
189
    }
190
191
    /**
192
     * Check if the given variable was defined in the current context before usage.
193
     *
194
     * @param \PHPMD\Node\ASTNode $variable
195
     * @param \PHPMD\Node\AbstractCallableNode $parentNode
196
     * @return bool
197
     */
198
    private function checkVariableDefined(ASTNode $variable, AbstractCallableNode $parentNode)
199
    {
200
        $image = $this->getVariableImage($variable);
0 ignored issues
show
Documentation introduced by
$variable is of type object<PHPMD\Node\ASTNode>, but the function expects a object<PDepend\Source\AS...\ASTVariableDeclarator>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
201
202
        return isset($this->images[$image]) || $this->isNameAllowedInContext($parentNode, $variable);
203
    }
204
205
    /**
206
     * Collect parameter names of method/function.
207
     *
208
     * @param \PHPMD\Node\AbstractNode $node
209
     * @return void
210
     */
211
    private function collectParameters(AbstractNode $node)
212
    {
213
        // Get formal parameter container
214
        $parameters = $node->getFirstChildOfType('FormalParameters');
215
216
        // Now get all declarators in the formal parameters container
217
        $declarators = $parameters->findChildrenOfType('VariableDeclarator');
218
219
        foreach ($declarators as $declarator) {
220
            $this->addVariableDefinition($declarator);
221
        }
222
    }
223
224
    /**
225
     * Collect assignments of variables.
226
     *
227
     * @param \PHPMD\Node\AbstractCallableNode $node
228
     * @return void
229
     */
230
    private function collectAssignments(AbstractCallableNode $node)
231
    {
232
        foreach ($node->findChildrenOfType('AssignmentExpression') as $assignment) {
233
            $variable = $assignment->getChild(0);
234
235
            if ($variable->getNode() instanceof ASTArray) {
236
                foreach ($variable->findChildrenOfType('Variable') as $unpackedVariable) {
237
                    $this->addVariableDefinition($unpackedVariable);
0 ignored issues
show
Documentation introduced by
$unpackedVariable is of type object<PHPMD\Node\ASTNode>, but the function expects a object<PDepend\Source\AS...\ASTVariableDeclarator>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
238
                }
239
240
                continue;
241
            }
242
243
            $this->addVariableDefinition($variable);
0 ignored issues
show
Documentation introduced by
$variable is of type object<PHPMD\Node\ASTNode>, but the function expects a object<PDepend\Source\AS...\ASTVariableDeclarator>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
244
        }
245
    }
246
247
    /**
248
     * Collect postfix property.
249
     *
250
     * @param \PHPMD\Node\AbstractNode $node
251
     * @return void
252
     */
253 View Code Duplication
    private function collectPropertyPostfix(AbstractNode $node)
254
    {
255
        $properties = $node->findChildrenOfType('PropertyPostfix');
256
257
        foreach ($properties as $property) {
258
            foreach ($property->getChildren() as $children) {
0 ignored issues
show
Documentation Bug introduced by
The method getChildren 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...
259
                if ($children instanceof ASTVariable) {
260
                    $this->addVariableDefinition($children);
261
                }
262
            }
263
        }
264
    }
265
266
    /**
267
     * Add the variable to images.
268
     *
269
     * @param ASTVariable|ASTPropertyPostfix|ASTVariableDeclarator $variable
270
     * @return void
271
     */
272
    private function addVariableDefinition($variable)
273
    {
274
        $image = $this->getVariableImage($variable);
275
276
        if (!isset($this->images[$image])) {
277
            $this->images[$image] = $variable;
278
        }
279
    }
280
281
    /**
282
     * Checks if a short name is acceptable in the current context.
283
     *
284
     * @param \PHPMD\Node\AbstractCallableNode $node
285
     * @param \PHPMD\Node\ASTNode $variable
286
     *
287
     * @return boolean
288
     */
289
    private function isNameAllowedInContext(AbstractCallableNode $node, ASTNode $variable)
290
    {
291
        return (
292
            $node instanceof MethodNode &&
293
            $variable->getImage() === '$this' &&
294
            ($node->getModifiers() & State::IS_STATIC) === 0
0 ignored issues
show
Documentation Bug introduced by
The method getModifiers does not exist on object<PHPMD\Node\MethodNode>? 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...
295
        );
296
    }
297
}
298