Passed
Push — master ( f3b68b...0617d8 )
by
unknown
01:27 queued 12s
created

UnusedFormalParameter::removeRegularVariables()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 3
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;
19
20
use PHPMD\AbstractNode;
21
use PHPMD\Node\ASTNode;
22
use PHPMD\Node\MethodNode;
23
24
/**
25
 * This rule collects all formal parameters of a given function or method that
26
 * are not used in a statement of the artifact's body.
27
 */
28
class UnusedFormalParameter extends AbstractLocalVariable implements FunctionAware, MethodAware
29
{
30
    /**
31
     * Collected ast nodes.
32
     *
33
     * @var \PHPMD\Node\ASTNode[]
34
     */
35
    private $nodes = array();
36
37
    /**
38
     * This method checks that all parameters of a given function or method are
39
     * used at least one time within the artifacts body.
40
     *
41
     * @param \PHPMD\AbstractNode $node
42
     * @return void
43
     */
44 33
    public function apply(AbstractNode $node)
45
    {
46 33
        if ($this->isAbstractMethod($node)) {
47 2
            return;
48
        }
49
50
        // Magic methods should be ignored as invalid declarations are picked up by PHP.
51 31
        if ($this->isMagicMethod($node)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->isMagicMethod($node) of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
52 1
            return;
53
        }
54
55 30
        if ($this->isInheritedSignature($node)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->isInheritedSignature($node) of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
56 2
            return;
57
        }
58
59 28
        if ($this->isNotDeclaration($node)) {
60 3
            return;
61
        }
62
63 25
        $this->nodes = array();
64
65 25
        $this->collectParameters($node);
66 25
        $this->removeUsedParameters($node);
67
68 25
        foreach ($this->nodes as $node) {
69 7
            $this->addViolation($node, array($node->getImage()));
70
        }
71 25
    }
72
73
    /**
74
     * Returns <b>true</b> when the given node is an abstract method.
75
     *
76
     * @param \PHPMD\AbstractNode $node
77
     * @return boolean
78
     */
79 33
    private function isAbstractMethod(AbstractNode $node)
80
    {
81 33
        if ($node instanceof MethodNode) {
82 30
            return $node->isAbstract();
83
        }
84 3
        return false;
85
    }
86
87
    /**
88
     * Returns <b>true</b> when the given node is method with signature declared as inherited using
89
     * {@inheritdoc} annotation.
90
     *
91
     * @param \PHPMD\AbstractNode $node
92
     * @return boolean
93
     */
94 30
    private function isInheritedSignature(AbstractNode $node)
95
    {
96 30
        if ($node instanceof MethodNode) {
97 27
            return preg_match('/\@inheritdoc/i', $node->getDocComment());
0 ignored issues
show
Documentation Bug introduced by
The method getDocComment 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...
98
        }
99 3
        return false;
100
    }
101
102
    /**
103
     * Returns <b>true</b> when the given node is a magic method signature
104
     * @param AbstractNode $node
105
     * @return boolean
106
     */
107 31
    private function isMagicMethod(AbstractNode $node)
108
    {
109 31
        static $names = array(
110
                'call',
111
                'callStatic',
112
                'get',
113
                'set',
114
                'isset',
115
                'unset',
116
                'set_state'
117
        );
118
119 31
        if ($node instanceof MethodNode) {
120 28
            return preg_match('/\__(?:' . implode("|", $names) . ')/i', $node->getName());
121
        }
122 3
        return false;
123
    }
124
125
    /**
126
     * Tests if the given <b>$node</b> is a method and if this method is also
127
     * the initial declaration.
128
     *
129
     * @param \PHPMD\AbstractNode $node
130
     * @return boolean
131
     * @since 1.2.1
132
     */
133 28
    private function isNotDeclaration(AbstractNode $node)
134
    {
135 28
        if ($node instanceof MethodNode) {
136 25
            return !$node->isDeclaration();
137
        }
138 3
        return false;
139
    }
140
141
    /**
142
     * This method extracts all parameters for the given function or method node
143
     * and it stores the parameter images in the <b>$_images</b> property.
144
     *
145
     * @param \PHPMD\AbstractNode $node
146
     * @return void
147
     */
148 25
    private function collectParameters(AbstractNode $node)
149
    {
150
        // First collect the formal parameters container
151 25
        $parameters = $node->getFirstChildOfType('FormalParameters');
152
153
        // Now get all declarators in the formal parameters container
154 25
        $declarators = $parameters->findChildrenOfType('VariableDeclarator');
155
156 25
        foreach ($declarators as $declarator) {
157 25
            $this->nodes[$declarator->getImage()] = $declarator;
158
        }
159 25
    }
160
161
    /**
162
     * This method collects all local variables in the body of the currently
163
     * analyzed method or function and removes those parameters that are
164
     * referenced by one of the collected variables.
165
     *
166
     * @param \PHPMD\AbstractNode $node
167
     * @return void
168
     */
169 25
    private function removeUsedParameters(AbstractNode $node)
170
    {
171 25
        $this->removeRegularVariables($node);
172 25
        $this->removeCompoundVariables($node);
173 25
        $this->removeVariablesUsedByFuncGetArgs($node);
174 25
    }
175
176
    /**
177
     * Removes all the regular variables from a given node 
178
     *
179
     * @param \PHPMD\AbstractNode $node The node to remove the regular variables from.
180
     * @return void
181
     */
182 25
    private function removeRegularVariables(AbstractNode $node)
183
    {
184 25
        $variables = $node->findChildrenOfType('Variable');
185 25
        foreach ($variables as $variable) {
186
            /** @var $variable ASTNode */
187 15
            if ($this->isRegularVariable($variable)) {
188 15
                unset($this->nodes[$variable->getImage()]);
189
            }
190
        }
191 25
    }
192
    
193
    /**
194
     * Removes all the compound variables from a given node 
195
     *
196
     * Such as
197
     *
198
     * <code>
199
     * //   ------
200
     * Foo::${BAR}();
201
     * //   ------
202
     *
203
     * //    ------
204
     * Foo::$${BAR}();
205
     * //    ------
206
     * </code>
207
     *
208
     * @param \PHPMD\AbstractNode $node The node to remove the compound variables from.
209
     * @return void
210
     */
211 25
    private function removeCompoundVariables(AbstractNode $node)
212
    {
213 25
        $compoundVariables = $node->findChildrenOfType('CompoundVariable');
214 25
        foreach ($compoundVariables as $compoundVariable) {
215 5
            $variablePrefix = $compoundVariable->getImage();
216
217 5
            foreach ($compoundVariable->findChildrenOfType('Expression') as $child) {
218 5
                $variableImage = $variablePrefix . $child->getImage();
219
220 5
                if (isset($this->nodes[$variableImage])) {
221 5
                    unset($this->nodes[$variableImage]);
222
                }
223
            }
224
        }
225 25
    }
226
227
    /**
228
     * Removes all the variables from a given node, if func_get_args() is called within
229
     *
230
     * If the given method calls func_get_args() then all parameters are automatically referenced.
231
     *
232
     * @param \PHPMD\AbstractNode $node The node to remove the referneced variables from.
233
     * @return void
234
     */
235 25
    private function removeVariablesUsedByFuncGetArgs(AbstractNode $node)
236
    {
237 25
        $functionCalls = $node->findChildrenOfType('FunctionPostfix');
238 25
        foreach ($functionCalls as $functionCall) {
239 8
            if ($this->isFunctionNameEqual($functionCall, 'func_get_args')) {
240 2
                $this->nodes = array();
241
            }
242
243 8
            if ($this->isFunctionNameEndingWith($functionCall, 'compact')) {
244 6
                foreach ($functionCall->findChildrenOfType('Literal') as $literal) {
245 8
                    unset($this->nodes['$' . trim($literal->getImage(), '"\'')]);
246
                }
247
            }
248
        }
249 25
    }
250
}
251