Passed
Push — feature/issue-718-handle-anony... ( f963d6 )
by Kyle
02:21
created

UndefinedVariable::collect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 10
rs 9.9332
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\CleanCode;
19
20
use PDepend\Source\AST\ASTClass;
21
use PDepend\Source\AST\ASTUnaryExpression;
22
use PDepend\Source\AST\ASTVariable;
23
use PDepend\Source\AST\State;
24
use PHPMD\AbstractNode;
25
use PHPMD\Node\AbstractCallableNode;
26
use PHPMD\Node\ASTNode;
27
use PHPMD\Node\MethodNode;
28
use PHPMD\Rule\AbstractLocalVariable;
29
use PHPMD\Rule\FunctionAware;
30
use PHPMD\Rule\MethodAware;
31
32
/**
33
 * This rule collects all undefined variables within a given function or method
34
 * that are used by any code in the analyzed source artifact.
35
 */
36
class UndefinedVariable extends AbstractLocalVariable implements FunctionAware, MethodAware
37
{
38
    /**
39
     * Found variable images within a single method or function.
40
     *
41
     * @var array(string)
42
     */
43
    private $images = array();
44
45
    /**
46
     * This method checks that all local variables within the given function or
47
     * method are used at least one time.
48
     *
49
     * @param \PHPMD\AbstractNode $node
50
     * @return void
51
     */
52
    public function apply(AbstractNode $node)
53
    {
54
        $this->images = array();
55
56
        $this->collect($node);
57
58
        foreach ($node->findChildrenOfType('Class') as $class) {
59
            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...
60
                $this->collect(new MethodNode($method));
61
            }
62
        }
63
64
        foreach ($node->findChildrenOfType('Variable') as $variable) {
65
            if (! $this->isNotSuperGlobal($variable)) {
66
                $this->addVariableDefinition($variable);
67
            }
68
            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...
69
                $this->addViolation($variable, array($variable->getImage()));
70
            }
71
        }
72
    }
73
74
    private function collect(AbstractNode $node) {
75
        $this->collectPropertyPostfix($node);
76
        $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...
77
        $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...
78
        $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...
79
        $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...
80
        $this->collectParameters($node);
81
        $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...
82
        $this->collectGlobalStatements($node);
83
    }
84
85
    /**
86
     * Stores the given literal node in an global of found variables.
87
     *
88
     * @param \PHPMD\Node\AbstractNode $node
89
     * @return void
90
     */
91
    private function collectGlobalStatements(AbstractNode $node)
92
    {
93
        $globalStatements = $node->findChildrenOfType('GlobalStatement');
94
95
        foreach ($globalStatements as $globalStatement) {
96
            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...
97
                $this->addVariableDefinition($variable);
98
            }
99
        }
100
    }
101
102
    /**
103
     * Stores the given literal node in an catch of found variables.
104
     *
105
     * @param \PHPMD\Node\AbstractCallableNode $node
106
     * @return void
107
     */
108 View Code Duplication
    private function collectExceptionCatches(AbstractCallableNode $node)
109
    {
110
        $catchStatements = $node->findChildrenOfType('CatchStatement');
111
112
        foreach ($catchStatements as $catchStatement) {
113
            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...
114
                if ($children instanceof ASTVariable) {
115
                    $this->addVariableDefinition($children);
116
                }
117
            }
118
        }
119
    }
120
121
    /**
122
     * Stores the given literal node in an internal list of found variables.
123
     *
124
     * @param \PHPMD\Node\AbstractCallableNode $node
125
     * @return void
126
     */
127 View Code Duplication
    private function collectListExpressions(AbstractCallableNode $node)
128
    {
129
        $lists = $node->findChildrenOfType('ListExpression');
130
131
        foreach ($lists as $listExpression) {
132
            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...
133
                $this->addVariableDefinition($variable);
134
            }
135
        }
136
    }
137
138
139
    /**
140
     * Stores the given literal node in an internal foreach of found variables.
141
     *
142
     * @param \PHPMD\Node\AbstractCallableNode $node
143
     * @return void
144
     */
145
    private function collectForeachStatements(AbstractCallableNode $node)
146
    {
147
        $foreachStatements = $node->findChildrenOfType('ForeachStatement');
148
149
        foreach ($foreachStatements as $foreachStatement) {
150
            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...
151
                if ($children instanceof ASTVariable) {
152
                    $this->addVariableDefinition($children);
153
                } elseif ($children instanceof ASTUnaryExpression) {
154
                    foreach ($children->getChildren() as $refChildren) {
155
                        if ($refChildren instanceof ASTVariable) {
156
                            $this->addVariableDefinition($refChildren);
157
                        }
158
                    }
159
                }
160
            }
161
        }
162
    }
163
164
    /**
165
     * Stores the given literal node in an internal closure of found variables.
166
     *
167
     * @param \PHPMD\Node\AbstractCallableNode $node
168
     * @return void
169
     */
170
    private function collectClosureParameters(AbstractCallableNode $node)
171
    {
172
        $closures = $node->findChildrenOfType('Closure');
173
174
        foreach ($closures as $closure) {
175
            $this->collectParameters($closure);
176
        }
177
    }
178
179
    /**
180
     * Check if the given variable was defined in the current context before usage.
181
     *
182
     * @param \PHPMD\Node\ASTNode $variable
183
     * @param \PHPMD\Node\AbstractCallableNode $parentNode
184
     * @return bool
185
     */
186
    private function checkVariableDefined(ASTNode $variable, AbstractCallableNode $parentNode)
187
    {
188
        return isset($this->images[$variable->getImage()]) || $this->isNameAllowedInContext($parentNode, $variable);
189
    }
190
191
    /**
192
     * Collect parameter names of method/function.
193
     *
194
     * @param \PHPMD\Node\AbstractNode $node
195
     * @return void
196
     */
197
    private function collectParameters(AbstractNode $node)
198
    {
199
        // Get formal parameter container
200
        $parameters = $node->getFirstChildOfType('FormalParameters');
201
202
        // Now get all declarators in the formal parameters container
203
        $declarators = $parameters->findChildrenOfType('VariableDeclarator');
204
205
        foreach ($declarators as $declarator) {
206
            $this->addVariableDefinition($declarator);
207
        }
208
    }
209
210
    /**
211
     * Collect assignments of variables.
212
     *
213
     * @param \PHPMD\Node\AbstractCallableNode $node
214
     * @return void
215
     */
216
    private function collectAssignments(AbstractCallableNode $node)
217
    {
218
        foreach ($node->findChildrenOfType('AssignmentExpression') as $assignment) {
219
            $variable = $assignment->getChild(0);
220
221
            $this->addVariableDefinition($variable);
222
        }
223
    }
224
225
    /**
226
     * Collect postfix property.
227
     *
228
     * @param \PHPMD\Node\AbstractNode $node
229
     * @return void
230
     */
231 View Code Duplication
    private function collectPropertyPostfix(AbstractNode $node)
232
    {
233
        $propertyes = $node->findChildrenOfType('PropertyPostfix');
234
235
        foreach ($propertyes as $property) {
236
            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...
237
                if ($children instanceof ASTVariable) {
238
                    $this->addVariableDefinition($children);
239
                }
240
            }
241
        }
242
    }
243
244
    /**
245
     * Add the variable to images
246
     *
247
     * @param mixed $variable
248
     * @return void
249
     */
250
    private function addVariableDefinition($variable)
251
    {
252
        if (! isset($this->images[$variable->getImage()])) {
253
            $this->images[$variable->getImage()] = $variable;
254
        }
255
    }
256
257
    /**
258
     * Checks if a short name is acceptable in the current context.
259
     *
260
     * @param \PHPMD\Node\AbstractCallableNode $node
261
     * @param \PHPMD\Node\ASTNode              $variable
262
     *
263
     * @return boolean
264
     */
265
    private function isNameAllowedInContext(AbstractCallableNode $node, ASTNode $variable)
266
    {
267
        return (
268
            $node instanceof MethodNode &&
269
            $variable->getImage() === '$this' &&
270
            ($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...
271
        );
272
    }
273
}
274