Completed
Push — feature/issue-367-phpcs-3.x-co... ( e185aa )
by Juliette
01:53
created

isCallTimePassByReferenceParam()   D

Complexity

Conditions 30
Paths 26

Size

Total Lines 108
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 108
rs 4.425
c 0
b 0
f 0
cc 30
eloc 60
nc 26
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     * Returns an array of tokens this test wants to listen for.
36
     *
37
     * @return array
38
     */
39
    public function register()
40
    {
41
        return array(T_STRING);
42
43
    }//end register()
44
45
    /**
46
     * Processes this test, when one of its tokens is encountered.
47
     *
48
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
49
     * @param int                   $stackPtr  The position of the current token
50
     *                                         in the stack passed in $tokens.
51
     *
52
     * @return void
53
     */
54
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
55
    {
56
        if ($this->supportsAbove('5.3') === false) {
57
            return;
58
        }
59
60
        $tokens = $phpcsFile->getTokens();
61
62
        // Skip tokens that are the names of functions or classes
63
        // within their definitions. For example: function myFunction...
64
        // "myFunction" is T_STRING but we should skip because it is not a
65
        // function or method *call*.
66
        $findTokens   = \PHP_CodeSniffer_Tokens::$emptyTokens;
67
        $findTokens[] = T_BITWISE_AND;
68
69
        $prevNonEmpty = $phpcsFile->findPrevious(
70
            $findTokens,
71
            ($stackPtr - 1),
72
            null,
73
            true
74
        );
75
76
        if ($prevNonEmpty !== false && in_array($tokens[$prevNonEmpty]['code'], array(T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT), true)) {
77
            return;
78
        }
79
80
        // If the next non-whitespace token after the function or method call
81
        // is not an opening parenthesis then it can't really be a *call*.
82
        $openBracket = $phpcsFile->findNext(
83
            \PHP_CodeSniffer_Tokens::$emptyTokens,
84
            ($stackPtr + 1),
85
            null,
86
            true
87
        );
88
89
        if ($openBracket === false || $tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS
90
            || isset($tokens[$openBracket]['parenthesis_closer']) === false
91
        ) {
92
            return;
93
        }
94
95
        // Get the function call parameters.
96
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
97
        if (count($parameters) === 0) {
98
            return;
99
        }
100
101
        // Which nesting level is the one we are interested in ?
102
        $nestedParenthesisCount = 1;
103 View Code Duplication
        if (isset($tokens[$openBracket]['nested_parenthesis'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across 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...
104
            $nestedParenthesisCount = count($tokens[$openBracket]['nested_parenthesis']) + 1;
105
        }
106
107
        foreach ($parameters as $parameter) {
108
            if ($this->isCallTimePassByReferenceParam($phpcsFile, $parameter, $nestedParenthesisCount) === true) {
109
                // T_BITWISE_AND represents a pass-by-reference.
110
                $error     = 'Using a call-time pass-by-reference is deprecated since PHP 5.3';
111
                $isError   = false;
112
                $errorCode = 'Deprecated';
113
114
                if ($this->supportsAbove('5.4')) {
115
                    $error    .= ' and prohibited since PHP 5.4';
116
                    $isError   = true;
117
                    $errorCode = 'NotAllowed';
118
                }
119
120
                $this->addMessage($phpcsFile, $error, $parameter['start'], $isError, $errorCode);
121
            }
122
        }
123
    }//end process()
124
125
126
    /**
127
     * Determine whether a parameter is passed by reference.
128
     *
129
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
130
     * @param array                 $parameter    Information on the current parameter
131
     *                                            to be examined.
132
     * @param int                   $nestingLevel Target nesting level.
133
     *
134
     * @return bool
135
     */
136
    protected function isCallTimePassByReferenceParam(\PHP_CodeSniffer_File $phpcsFile, $parameter, $nestingLevel)
137
    {
138
        $tokens   = $phpcsFile->getTokens();
139
140
        $searchStartToken = $parameter['start'] - 1;
141
        $searchEndToken   = $parameter['end'] + 1;
142
        $nextVariable     = $searchStartToken;
143
        do {
144
            $nextVariable = $phpcsFile->findNext(T_VARIABLE, ($nextVariable + 1), $searchEndToken);
145
            if ($nextVariable === false) {
146
                return false;
147
            }
148
149
            // Make sure the variable belongs directly to this function call
150
            // and is not inside a nested function call or array.
151
            if (isset($tokens[$nextVariable]['nested_parenthesis']) === false
152
                || (count($tokens[$nextVariable]['nested_parenthesis']) !== $nestingLevel)
153
            ) {
154
                continue;
155
            }
156
157
158
            // 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...
159
            $tokenBefore = $phpcsFile->findPrevious(
160
                \PHP_CodeSniffer_Tokens::$emptyTokens,
161
                ($nextVariable - 1),
162
                $searchStartToken,
163
                true
164
            );
165
166
            if ($tokenBefore === false || $tokens[$tokenBefore]['code'] !== T_BITWISE_AND) {
167
                // Nothing before the token or no &.
168
                continue;
169
            }
170
171
            // 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...
172
            $tokenBefore = $phpcsFile->findPrevious(
173
                \PHP_CodeSniffer_Tokens::$emptyTokens,
174
                ($tokenBefore - 1),
175
                $searchStartToken,
176
                true
177
            );
178
179
            // We have to exclude all uses of T_BITWISE_AND that are not
180
            // references. We use a blacklist approach as we prefer false
181
            // positives to not identifying a pass-by-reference call at all.
182
            // The blacklist may not yet be complete.
183
            switch ($tokens[$tokenBefore]['code']) {
184
                // In these cases T_BITWISE_AND represents
185
                // the bitwise and operator.
186
                case T_LNUMBER:
187
                case T_VARIABLE:
188
                case T_CLOSE_SQUARE_BRACKET:
189
                case T_CLOSE_PARENTHESIS:
190
                    break;
191
192
                // Prevent false positive on assign by reference and compare with reference
193
                // with function call parameters.
194
                case T_EQUAL:
195
                case T_AND_EQUAL:
196
                case T_OR_EQUAL:
197
                case T_CONCAT_EQUAL:
198
                case T_DIV_EQUAL:
199
                case T_MINUS_EQUAL:
200
                case T_MOD_EQUAL:
201
                case T_MUL_EQUAL:
202
                case T_PLUS_EQUAL:
203
                case T_XOR_EQUAL:
204
                case T_SL_EQUAL:
205
                case T_SR_EQUAL:
206
                case T_IS_EQUAL:
207
                case T_IS_IDENTICAL:
208
                    break;
209
210
                // Unfortunately the tokenizer fails to recognize global constants,
211
                // class-constants and -attributes. Any of these are returned is
212
                // treated as T_STRING.
213
                // So we step back another token and check if it is a class
214
                // operator (-> or ::), which means we have a false positive.
215
                // Global constants still remain uncovered.
216
                case T_STRING:
217
                    $tokenBeforePlus = $phpcsFile->findPrevious(
218
                        \PHP_CodeSniffer_Tokens::$emptyTokens,
219
                        ($tokenBefore - 1),
220
                        $searchStartToken,
221
                        true
222
                    );
223
                    if ($tokens[$tokenBeforePlus]['code'] === T_DOUBLE_COLON
224
                        || $tokens[$tokenBeforePlus]['code'] === T_OBJECT_OPERATOR
225
                    ) {
226
                        break;
227
                    }
228
                    // If not a class constant: fall through.
229
230
                default:
231
                    // Deal with T_POW_EQUAL which doesn't exist in PHPCS 1.x.
232
                    if (defined('T_POW_EQUAL') && $tokens[$tokenBefore]['type'] === 'T_POW_EQUAL') {
233
                        break;
234
                    }
235
236
                    // The found T_BITWISE_AND represents a pass-by-reference.
237
                    return true;
238
            }
239
        } while ($nextVariable < $searchEndToken);
240
241
        // This code should never be reached, but here in case of weird bugs ;-)
242
        return false;
243
    }
244
245
}//end class
246