Completed
Push — changelog-8.1.0 ( 3ad90e...282a44 )
by Juliette
04:23
created

ForbiddenCallTimePassByReferenceSniff   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 203
Duplicated Lines 1.48 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 1
dl 3
loc 203
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 5 1
C process() 3 70 12
C isCallTimePassByReferenceParam() 0 60 9

How to fix   Duplicated Code   

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:

1
<?php
2
/**
3
 * \PHPCompatibility\Sniffs\PHP\ForbiddenCallTimePassByReference.
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  PHP
8
 * @package   PHPCompatibility
9
 * @author    Gary Rogers <[email protected]>
10
 * @author    Florian Grandel <[email protected]>
11
 * @copyright 2009 Florian Grandel
12
 */
13
14
namespace PHPCompatibility\Sniffs\PHP;
15
16
use PHPCompatibility\Sniff;
17
18
/**
19
 * \PHPCompatibility\Sniffs\PHP\ForbiddenCallTimePassByReference.
20
 *
21
 * Discourages the use of call time pass by references
22
 *
23
 * PHP version 5.4
24
 *
25
 * @category  PHP
26
 * @package   PHPCompatibility
27
 * @author    Gary Rogers <[email protected]>
28
 * @author    Florian Grandel <[email protected]>
29
 * @copyright 2009 Florian Grandel
30
 */
31
class ForbiddenCallTimePassByReferenceSniff extends Sniff
32
{
33
34
    /**
35
     * Tokens that represent assignments or equality comparisons.
36
     *
37
     * Near duplicate of Tokens::$assignmentTokens + Tokens::$equalityTokens.
38
     * Copied in for PHPCS cross-version compatibility.
39
     *
40
     * @var array
41
     */
42
    private $assignOrCompare = array(
43
        // Comparison tokens.
44
        'T_IS_EQUAL'            => true,
45
        'T_IS_NOT_EQUAL'        => true,
46
        'T_IS_IDENTICAL'        => true,
47
        'T_IS_NOT_IDENTICAL'    => true,
48
        'T_IS_SMALLER_OR_EQUAL' => true,
49
        'T_IS_GREATER_OR_EQUAL' => true,
50
51
        // Assignment tokens.
52
        'T_EQUAL'          => true,
53
        'T_AND_EQUAL'      => true,
54
        'T_OR_EQUAL'       => true,
55
        'T_CONCAT_EQUAL'   => true,
56
        'T_DIV_EQUAL'      => true,
57
        'T_MINUS_EQUAL'    => true,
58
        'T_POW_EQUAL'      => true,
59
        'T_MOD_EQUAL'      => true,
60
        'T_MUL_EQUAL'      => true,
61
        'T_PLUS_EQUAL'     => true,
62
        'T_XOR_EQUAL'      => true,
63
        'T_DOUBLE_ARROW'   => true,
64
        'T_SL_EQUAL'       => true,
65
        'T_SR_EQUAL'       => true,
66
        'T_COALESCE_EQUAL' => true,
67
        'T_ZSR_EQUAL'      => true,
68
    );
69
70
    /**
71
     * Returns an array of tokens this test wants to listen for.
72
     *
73
     * @return array
74
     */
75
    public function register()
76
    {
77
        return array(T_STRING);
78
79
    }//end register()
80
81
    /**
82
     * Processes this test, when one of its tokens is encountered.
83
     *
84
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
85
     * @param int                   $stackPtr  The position of the current token
86
     *                                         in the stack passed in $tokens.
87
     *
88
     * @return void
89
     */
90
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
91
    {
92
        if ($this->supportsAbove('5.3') === false) {
93
            return;
94
        }
95
96
        $tokens = $phpcsFile->getTokens();
97
98
        // Skip tokens that are the names of functions or classes
99
        // within their definitions. For example: function myFunction...
100
        // "myFunction" is T_STRING but we should skip because it is not a
101
        // function or method *call*.
102
        $findTokens   = \PHP_CodeSniffer_Tokens::$emptyTokens;
103
        $findTokens[] = T_BITWISE_AND;
104
105
        $prevNonEmpty = $phpcsFile->findPrevious(
106
            $findTokens,
107
            ($stackPtr - 1),
108
            null,
109
            true
110
        );
111
112
        if ($prevNonEmpty !== false && in_array($tokens[$prevNonEmpty]['type'], array('T_FUNCTION', 'T_CLASS', 'T_INTERFACE', 'T_TRAIT'), true)) {
113
            return;
114
        }
115
116
        // If the next non-whitespace token after the function or method call
117
        // is not an opening parenthesis then it can't really be a *call*.
118
        $openBracket = $phpcsFile->findNext(
119
            \PHP_CodeSniffer_Tokens::$emptyTokens,
120
            ($stackPtr + 1),
121
            null,
122
            true
123
        );
124
125
        if ($openBracket === false || $tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS
126
            || isset($tokens[$openBracket]['parenthesis_closer']) === false
127
        ) {
128
            return;
129
        }
130
131
        // Get the function call parameters.
132
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
133
        if (count($parameters) === 0) {
134
            return;
135
        }
136
137
        // Which nesting level is the one we are interested in ?
138
        $nestedParenthesisCount = 1;
139 View Code Duplication
        if (isset($tokens[$openBracket]['nested_parenthesis'])) {
140
            $nestedParenthesisCount = count($tokens[$openBracket]['nested_parenthesis']) + 1;
141
        }
142
143
        foreach ($parameters as $parameter) {
144
            if ($this->isCallTimePassByReferenceParam($phpcsFile, $parameter, $nestedParenthesisCount) === true) {
145
                // T_BITWISE_AND represents a pass-by-reference.
146
                $error     = 'Using a call-time pass-by-reference is deprecated since PHP 5.3';
147
                $isError   = false;
148
                $errorCode = 'Deprecated';
149
150
                if ($this->supportsAbove('5.4')) {
151
                    $error    .= ' and prohibited since PHP 5.4';
152
                    $isError   = true;
153
                    $errorCode = 'NotAllowed';
154
                }
155
156
                $this->addMessage($phpcsFile, $error, $parameter['start'], $isError, $errorCode);
157
            }
158
        }
159
    }//end process()
160
161
162
    /**
163
     * Determine whether a parameter is passed by reference.
164
     *
165
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
166
     * @param array                 $parameter    Information on the current parameter
167
     *                                            to be examined.
168
     * @param int                   $nestingLevel Target nesting level.
169
     *
170
     * @return bool
171
     */
172
    protected function isCallTimePassByReferenceParam(\PHP_CodeSniffer_File $phpcsFile, $parameter, $nestingLevel)
173
    {
174
        $tokens = $phpcsFile->getTokens();
175
176
        $searchStartToken = $parameter['start'] - 1;
177
        $searchEndToken   = $parameter['end'] + 1;
178
        $nextVariable     = $searchStartToken;
179
        do {
180
            $nextVariable = $phpcsFile->findNext(T_VARIABLE, ($nextVariable + 1), $searchEndToken);
181
            if ($nextVariable === false) {
182
                return false;
183
            }
184
185
            // Make sure the variable belongs directly to this function call
186
            // and is not inside a nested function call or array.
187
            if (isset($tokens[$nextVariable]['nested_parenthesis']) === false
188
                || (count($tokens[$nextVariable]['nested_parenthesis']) !== $nestingLevel)
189
            ) {
190
                continue;
191
            }
192
193
            // Checking this: $value = my_function(...[*]$arg...).
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
194
            $tokenBefore = $phpcsFile->findPrevious(
195
                \PHP_CodeSniffer_Tokens::$emptyTokens,
196
                ($nextVariable - 1),
197
                $searchStartToken,
198
                true
199
            );
200
201
            if ($tokenBefore === false || $tokens[$tokenBefore]['code'] !== T_BITWISE_AND) {
202
                // Nothing before the token or no &.
203
                continue;
204
            }
205
206
            if ($phpcsFile->isReference($tokenBefore) === false) {
207
                continue;
208
            }
209
210
            // Checking this: $value = my_function(...[*]&$arg...).
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
211
            $tokenBefore = $phpcsFile->findPrevious(
212
                \PHP_CodeSniffer_Tokens::$emptyTokens,
213
                ($tokenBefore - 1),
214
                $searchStartToken,
215
                true
216
            );
217
218
            // Prevent false positive on assign by reference and compare with reference
219
            // within function call parameters.
220
            if (isset($this->assignOrCompare[$tokens[$tokenBefore]['type']])) {
221
                continue;
222
            }
223
224
            // The found T_BITWISE_AND represents a pass-by-reference.
225
            return true;
226
227
        } while ($nextVariable < $searchEndToken);
228
229
        // This code should never be reached, but here in case of weird bugs ;-)
230
        return false;
231
    }
232
233
}//end class
234