UnusedLocalVariable::getExceptionsList()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 10
loc 10
rs 9.9332
c 0
b 0
f 0
ccs 5
cts 5
cp 1
crap 2
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 PHPMD\AbstractNode;
21
use PHPMD\Node\AbstractCallableNode;
22
use PHPMD\Node\ASTNode;
23
24
/**
25
 * This rule collects all local variables within a given function or method
26
 * that are not used by any code in the analyzed source artifact.
27
 */
28
class UnusedLocalVariable extends AbstractLocalVariable implements FunctionAware, MethodAware
29
{
30
    /**
31
     * Found variable images within a single method or function.
32
     *
33
     * @var array(string)
34
     */
35
    private $images = array();
36
37
    /**
38
     * This method checks that all local variables within the given function or
39
     * method are used at least one time.
40
     *
41
     * @param \PHPMD\AbstractNode $node
42
     * @return void
43
     */
44 44
    public function apply(AbstractNode $node)
45
    {
46 44
        $this->images = array();
47
48
        /** @var $node AbstractCallableNode */
49 44
        $this->collectVariables($node);
50 44
        $this->removeParameters($node);
51
52 44
        foreach ($this->images as $nodes) {
53 27
            if (count($nodes) === 1) {
54 27
                $this->doCheckNodeImage($nodes[0]);
55
            }
56
        }
57 44
    }
58
59
    /**
60
     * This method removes all variables from the <b>$_images</b> property that
61
     * are also found in the formal parameters of the given method or/and
62
     * function node.
63
     *
64
     * @param \PHPMD\Node\AbstractCallableNode $node
65
     * @return void
66
     */
67 44
    private function removeParameters(AbstractCallableNode $node)
68
    {
69
        // Get formal parameter container
70 44
        $parameters = $node->getFirstChildOfType('FormalParameters');
71
72
        // Now get all declarators in the formal parameters container
73 44
        $declarators = $parameters->findChildrenOfType('VariableDeclarator');
74
75 44
        foreach ($declarators as $declarator) {
76 3
            unset($this->images[$declarator->getImage()]);
77
        }
78 44
    }
79
80
    /**
81
     * This method collects all local variable instances from the given
82
     * method/function node and stores their image in the <b>$_images</b>
83
     * property.
84
     *
85
     *
86
     * @param \PHPMD\Node\AbstractCallableNode $node
87
     * @return void
88
     */
89 44
    private function collectVariables(AbstractCallableNode $node)
90
    {
91 44
        foreach ($node->findChildrenOfType('Variable') as $variable) {
92
            /** @var $variable ASTNode */
93 43
            if ($this->isLocal($variable)) {
94 43
                $this->collectVariable($variable);
95
            }
96
        }
97
98 44
        foreach ($node->findChildrenOfType('CompoundVariable') as $variable) {
99 2
            $this->collectCompoundVariableInString($variable);
0 ignored issues
show
Compatibility introduced by
$variable of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\ASTNode>. 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...
100
        }
101
102 44
        foreach ($node->findChildrenOfType('VariableDeclarator') as $variable) {
103 5
            $this->collectVariable($variable);
0 ignored issues
show
Compatibility introduced by
$variable of type object<PHPMD\AbstractNode> is not a sub-type of object<PHPMD\Node\ASTNode>. 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...
104
        }
105 44
        foreach ($node->findChildrenOfType('FunctionPostfix') as $func) {
106 4
            if ($this->isFunctionNameEndingWith($func, 'compact')) {
107 4
                foreach ($func->findChildrenOfType('Literal') as $literal) {
108
                    /** @var $literal ASTNode */
109 4
                    $this->collectLiteral($literal);
110
                }
111
            }
112
        }
113 44
    }
114
115
    /**
116
     * Stores the given compound variable node in an internal list of found variables.
117
     *
118
     * @param \PHPMD\Node\ASTNode $node
119
     * @return void
120
     */
121 2
    private function collectCompoundVariableInString(ASTNode $node)
122
    {
123 2
        $parentNode = $node->getParent()->getNode();
124 2
        $candidateParentNodes = $node->getParentsOfType('PDepend\Source\AST\ASTString');
0 ignored issues
show
Documentation Bug introduced by
The method getParentsOfType 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...
125
126 2
        if (in_array($parentNode, $candidateParentNodes)) {
127 1
            $variablePrefix = $node->getImage();
128
129 1
            foreach ($node->findChildrenOfType('Expression') as $child) {
130 1
                $variableName = $child->getImage();
131 1
                $variableImage = $variablePrefix . $variableName;
132
133 1
                $this->storeImage($variableImage, $node);
134
            }
135
        }
136 2
    }
137
138
    /**
139
     * Stores the given variable node in an internal list of found variables.
140
     *
141
     * @param \PHPMD\Node\ASTNode $node
142
     * @return void
143
     */
144 28
    private function collectVariable(ASTNode $node)
145
    {
146 28
        $imageName = $node->getImage();
147 28
        $this->storeImage($imageName, $node);
148 28
    }
149
150
    /**
151
     * Safely add node to $this->images.
152
     *
153
     * @param string $imageName         the name to store the node as
154
     * @param \PHPMD\Node\ASTNode $node the node being stored
155
     * @return void
156
     */
157 28
    private function storeImage($imageName, ASTNode $node)
158
    {
159 28
        if (!isset($this->images[$imageName])) {
160 28
            $this->images[$imageName] = array();
161
        }
162 28
        $this->images[$imageName][] = $node;
163 28
    }
164
165
    /**
166
     * Stores the given literal node in an internal list of found variables.
167
     *
168
     * @param \PHPMD\Node\ASTNode $node
169
     * @return void
170
     */
171 4
    private function collectLiteral(ASTNode $node)
172
    {
173 4
        $variable = '$' . trim($node->getImage(), '\'');
174 4
        if (!isset($this->images[$variable])) {
175
            $this->images[$variable] = array();
176
        }
177 4
        $this->images[$variable][] = $node;
178 4
    }
179
180
    /**
181
     * Template method that performs the real node image check.
182
     *
183
     * @param ASTNode $node
184
     * @return void
185
     */
186 15
    protected function doCheckNodeImage(ASTNode $node)
187
    {
188 15
        if ($this->isNameAllowedInContext($node)) {
189 1
            return;
190
        }
191 14
        if ($this->isUnusedForeachVariableAllowed($node)) {
192 2
            return;
193
        }
194 12
        $exceptions = $this->getExceptionsList();
195 12
        if (in_array(substr($node->getImage(), 1), $exceptions)) {
196 3
            return;
197
        }
198 10
        $this->addViolation($node, array($node->getImage()));
199 10
    }
200
201
    /**
202
     * Checks if a short name is acceptable in the current context. For the
203
     * moment these contexts are the init section of a for-loop and short
204
     * variable names in catch-statements.
205
     *
206
     * @param \PHPMD\AbstractNode $node
207
     * @return boolean
208
     */
209 15
    private function isNameAllowedInContext(AbstractNode $node)
210
    {
211 15
        return $this->isChildOf($node, 'CatchStatement');
212
    }
213
214
    /**
215
     * Checks if an unused foreach variable (key or variable) is allowed.
216
     *
217
     * If it's not a foreach variable, it returns always false.
218
     *
219
     * @param \PHPMD\Node\ASTNode $variable The variable to check.
220
     * @return bool True if allowed, else false.
221
     */
222 14
    private function isUnusedForeachVariableAllowed(ASTNode $variable)
223
    {
224 14
        $isForeachVariable = $this->isChildOf($variable, 'ForeachStatement');
225 14
        if (!$isForeachVariable) {
226 9
            return false;
227
        }
228
229 5
        return $this->getBooleanProperty('allow-unused-foreach-variables');
230
    }
231
232
    /**
233
     * Checks if the given node is a direct or indirect child of a node with
234
     * the given type.
235
     *
236
     * @param \PHPMD\AbstractNode $node
237
     * @param string $type
238
     * @return boolean
239
     */
240 15
    private function isChildOf(AbstractNode $node, $type)
241
    {
242 15
        $parent = $node->getParent();
243
244 15
        return $parent->isInstanceOf($type);
245
    }
246
247
    /**
248
     * Gets array of exceptions from property
249
     *
250
     * @return array
251
     */
252 12 View Code Duplication
    private function getExceptionsList()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
    {
254
        try {
255 12
            $exceptions = $this->getStringProperty('exceptions');
256 9
        } catch (\OutOfBoundsException $e) {
257 9
            $exceptions = '';
258
        }
259
260 12
        return explode(',', $exceptions);
261
    }
262
}
263