Sniff::isShortList()   F
last analyzed

Complexity

Conditions 32
Paths 132

Size

Total Lines 148

Duplication

Lines 6
Ratio 4.05 %

Importance

Changes 0
Metric Value
dl 6
loc 148
rs 3.1199
c 0
b 0
f 0
cc 32
nc 132
nop 2

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\Sniff.
4
 *
5
 * @category  PHP
6
 * @package   PHPCompatibility
7
 * @author    Wim Godden <[email protected]>
8
 * @copyright 2014 Cu.be Solutions bvba
9
 */
10
11
namespace PHPCompatibility;
12
13
use PHPCompatibility\PHPCSHelper;
14
15
/**
16
 * \PHPCompatibility\Sniff.
17
 *
18
 * @category  PHP
19
 * @package   PHPCompatibility
20
 * @author    Wim Godden <[email protected]>
21
 * @copyright 2014 Cu.be Solutions bvba
22
 */
23
abstract class Sniff implements \PHP_CodeSniffer_Sniff
24
{
25
26
    const REGEX_COMPLEX_VARS = '`(?:(\{)?(?<!\\\\)\$)?(\{)?(?<!\\\\)\$(\{)?(?P<varname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:->\$?(?P>varname)|\[[^\]]+\]|::\$?(?P>varname)|\([^\)]*\))*(?(3)\}|)(?(2)\}|)(?(1)\}|)`';
27
28
    /**
29
     * List of superglobals as an array of strings.
30
     *
31
     * Used by the ParameterShadowSuperGlobals and ForbiddenClosureUseVariableNames sniffs.
32
     *
33
     * @var array
34
     */
35
    protected $superglobals = array(
36
        '$GLOBALS',
37
        '$_SERVER',
38
        '$_GET',
39
        '$_POST',
40
        '$_FILES',
41
        '$_COOKIE',
42
        '$_SESSION',
43
        '$_REQUEST',
44
        '$_ENV',
45
    );
46
47
    /**
48
     * List of functions using hash algorithm as parameter (always the first parameter).
49
     *
50
     * Used by the new/removed hash algorithm sniffs.
51
     * Key is the function name, value is the 1-based parameter position in the function call.
52
     *
53
     * @var array
54
     */
55
    protected $hashAlgoFunctions = array(
56
        'hash_file'      => 1,
57
        'hash_hmac_file' => 1,
58
        'hash_hmac'      => 1,
59
        'hash_init'      => 1,
60
        'hash_pbkdf2'    => 1,
61
        'hash'           => 1,
62
    );
63
64
65
    /**
66
     * List of functions which take an ini directive as parameter (always the first parameter).
67
     *
68
     * Used by the new/removed ini directives sniffs.
69
     * Key is the function name, value is the 1-based parameter position in the function call.
70
     *
71
     * @var array
72
     */
73
    protected $iniFunctions = array(
74
        'ini_get' => 1,
75
        'ini_set' => 1,
76
    );
77
78
79
    /**
80
     * Get the testVersion configuration variable.
81
     *
82
     * The testVersion configuration variable may be in any of the following formats:
83
     * 1) Omitted/empty, in which case no version is specified. This effectively
84
     *    disables all the checks for new PHP features provided by this standard.
85
     * 2) A single PHP version number, e.g. "5.4" in which case the standard checks that
86
     *    the code will run on that version of PHP (no deprecated features or newer
87
     *    features being used).
88
     * 3) A range, e.g. "5.0-5.5", in which case the standard checks the code will run
89
     *    on all PHP versions in that range, and that it doesn't use any features that
90
     *    were deprecated by the final version in the list, or which were not available
91
     *    for the first version in the list.
92
     *    We accept ranges where one of the components is missing, e.g. "-5.6" means
93
     *    all versions up to PHP 5.6, and "7.0-" means all versions above PHP 7.0.
94
     * PHP version numbers should always be in Major.Minor format.  Both "5", "5.3.2"
95
     * would be treated as invalid, and ignored.
96
     *
97
     * @return array $arrTestVersions will hold an array containing min/max version
98
     *               of PHP that we are checking against (see above).  If only a
99
     *               single version number is specified, then this is used as
100
     *               both the min and max.
101
     *
102
     * @throws \PHP_CodeSniffer_Exception If testVersion is invalid.
103
     */
104
    private function getTestVersion()
105
    {
106
        static $arrTestVersions = array();
107
108
        $default     = array(null, null);
109
        $testVersion = trim(PHPCSHelper::getConfigData('testVersion'));
110
111
        if (empty($testVersion) === false && isset($arrTestVersions[$testVersion]) === false) {
112
113
            $arrTestVersions[$testVersion] = $default;
114
115
            if (preg_match('`^\d+\.\d+$`', $testVersion)) {
116
                $arrTestVersions[$testVersion] = array($testVersion, $testVersion);
117
                return $arrTestVersions[$testVersion];
118
            }
119
120
            if (preg_match('`^(\d+\.\d+)?\s*-\s*(\d+\.\d+)?$`', $testVersion, $matches)) {
121
                if (empty($matches[1]) === false || empty($matches[2]) === false) {
122
                    // If no lower-limit is set, we set the min version to 4.0.
123
                    // Whilst development focuses on PHP 5 and above, we also accept
124
                    // sniffs for PHP 4, so we include that as the minimum.
125
                    // (It makes no sense to support PHP 3 as this was effectively a
126
                    // different language).
127
                    $min = empty($matches[1]) ? '4.0' : $matches[1];
128
129
                    // If no upper-limit is set, we set the max version to 99.9.
130
                    $max = empty($matches[2]) ? '99.9' : $matches[2];
131
132
                    if (version_compare($min, $max, '>')) {
133
                        trigger_error(
134
                            "Invalid range in testVersion setting: '" . $testVersion . "'",
135
                            E_USER_WARNING
136
                        );
137
                        return $default;
138
                    }
139
                    else {
140
                        $arrTestVersions[$testVersion] = array($min, $max);
141
                        return $arrTestVersions[$testVersion];
142
                    }
143
                }
144
            }
145
146
            trigger_error(
147
                "Invalid testVersion setting: '" . $testVersion . "'",
148
                E_USER_WARNING
149
            );
150
            return $default;
151
        }
152
153
        if (isset($arrTestVersions[$testVersion])) {
154
            return $arrTestVersions[$testVersion];
155
        }
156
157
        return $default;
158
    }
159
160
161
    /**
162
     * Check whether a specific PHP version is equal to or higher than the maximum
163
     * supported PHP version as provided by the user in `testVersion`.
164
     *
165
     * Should be used when sniffing for *old* PHP features (deprecated/removed).
166
     *
167
     * @param string $phpVersion A PHP version number in 'major.minor' format.
168
     *
169
     * @return bool True if testVersion has not been provided or if the PHP version
170
     *              is equal to or higher than the highest supported PHP version
171
     *              in testVersion. False otherwise.
172
     */
173 View Code Duplication
    public function supportsAbove($phpVersion)
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...
174
    {
175
        $testVersion = $this->getTestVersion();
176
        $testVersion = $testVersion[1];
177
178
        if (is_null($testVersion)
179
            || version_compare($testVersion, $phpVersion) >= 0
180
        ) {
181
            return true;
182
        } else {
183
            return false;
184
        }
185
    }//end supportsAbove()
186
187
188
    /**
189
     * Check whether a specific PHP version is equal to or lower than the minimum
190
     * supported PHP version as provided by the user in `testVersion`.
191
     *
192
     * Should be used when sniffing for *new* PHP features.
193
     *
194
     * @param string $phpVersion A PHP version number in 'major.minor' format.
195
     *
196
     * @return bool True if the PHP version is equal to or lower than the lowest
197
     *              supported PHP version in testVersion.
198
     *              False otherwise or if no testVersion is provided.
199
     */
200 View Code Duplication
    public function supportsBelow($phpVersion)
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...
201
    {
202
        $testVersion = $this->getTestVersion();
203
        $testVersion = $testVersion[0];
204
205
        if (is_null($testVersion) === false
206
            && version_compare($testVersion, $phpVersion) <= 0
207
        ) {
208
            return true;
209
        } else {
210
            return false;
211
        }
212
    }//end supportsBelow()
213
214
215
    /**
216
     * Add a PHPCS message to the output stack as either a warning or an error.
217
     *
218
     * @param \PHP_CodeSniffer_File $phpcsFile The file the message applies to.
219
     * @param string                $message   The message.
220
     * @param int                   $stackPtr  The position of the token
221
     *                                         the message relates to.
222
     * @param bool                  $isError   Whether to report the message as an
223
     *                                         'error' or 'warning'.
224
     *                                         Defaults to true (error).
225
     * @param string                $code      The error code for the message.
226
     *                                         Defaults to 'Found'.
227
     * @param array                 $data      Optional input for the data replacements.
228
     *
229
     * @return void
230
     */
231
    public function addMessage(\PHP_CodeSniffer_File $phpcsFile, $message, $stackPtr, $isError, $code = 'Found', $data = array())
232
    {
233
        if ($isError === true) {
234
            $phpcsFile->addError($message, $stackPtr, $code, $data);
235
        } else {
236
            $phpcsFile->addWarning($message, $stackPtr, $code, $data);
237
        }
238
    }
239
240
241
    /**
242
     * Convert an arbitrary string to an alphanumeric string with underscores.
243
     *
244
     * Pre-empt issues with arbitrary strings being used as error codes in XML and PHP.
245
     *
246
     * @param string $baseString Arbitrary string.
247
     *
248
     * @return string
249
     */
250
    public function stringToErrorCode($baseString)
251
    {
252
        return preg_replace('`[^a-z0-9_]`i', '_', strtolower($baseString));
253
    }
254
255
256
    /**
257
     * Strip quotes surrounding an arbitrary string.
258
     *
259
     * Intended for use with the content of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
260
     *
261
     * @param string $string The raw string.
262
     *
263
     * @return string String without quotes around it.
264
     */
265
    public function stripQuotes($string)
266
    {
267
        return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
268
    }
269
270
271
    /**
272
     * Strip variables from an arbitrary double quoted string.
273
     *
274
     * Intended for use with the content of a T_DOUBLE_QUOTED_STRING.
275
     *
276
     * @param string $string The raw string.
277
     *
278
     * @return string String without variables in it.
279
     */
280
    public function stripVariables($string)
281
    {
282
        if (strpos($string, '$') === false) {
283
            return $string;
284
        }
285
286
        return preg_replace(self::REGEX_COMPLEX_VARS, '', $string);
287
    }
288
289
290
    /**
291
     * Make all top level array keys in an array lowercase.
292
     *
293
     * @param array $array Initial array.
294
     *
295
     * @return array Same array, but with all lowercase top level keys.
296
     */
297
    public function arrayKeysToLowercase($array)
298
    {
299
        $keys = array_keys($array);
300
        $keys = array_map('strtolower', $keys);
301
        return array_combine($keys, $array);
302
    }
303
304
305
    /**
306
     * Checks if a function call has parameters.
307
     *
308
     * Expects to be passed the T_STRING stack pointer for the function call.
309
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
310
     *
311
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it
312
     * will detect whether the array has values or is empty.
313
     *
314
     * @link https://github.com/wimg/PHPCompatibility/issues/120
315
     * @link https://github.com/wimg/PHPCompatibility/issues/152
316
     *
317
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
318
     * @param int                   $stackPtr  The position of the function call token.
319
     *
320
     * @return bool
321
     */
322
    public function doesFunctionCallHaveParameters(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
323
    {
324
        $tokens = $phpcsFile->getTokens();
325
326
        // Check for the existence of the token.
327
        if (isset($tokens[$stackPtr]) === false) {
328
            return false;
329
        }
330
331
        // Is this one of the tokens this function handles ?
332
        if (in_array($tokens[$stackPtr]['code'], array(T_STRING, T_ARRAY, T_OPEN_SHORT_ARRAY), true) === false) {
333
            return false;
334
        }
335
336
        $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
337
338
        // Deal with short array syntax.
339
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) {
340
            if (isset($tokens[$stackPtr]['bracket_closer']) === false) {
341
                return false;
342
            }
343
344
            if ($nextNonEmpty === $tokens[$stackPtr]['bracket_closer']) {
345
                // No parameters.
346
                return false;
347
            } else {
348
                return true;
349
            }
350
        }
351
352
        // Deal with function calls & long arrays.
353
        // Next non-empty token should be the open parenthesis.
354
        if ($nextNonEmpty === false && $tokens[$nextNonEmpty]['code'] !== T_OPEN_PARENTHESIS) {
355
            return false;
356
        }
357
358
        if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
359
            return false;
360
        }
361
362
        $closeParenthesis = $tokens[$nextNonEmpty]['parenthesis_closer'];
363
        $nextNextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $nextNonEmpty + 1, $closeParenthesis + 1, true);
364
365
        if ($nextNextNonEmpty === $closeParenthesis) {
366
            // No parameters.
367
            return false;
368
        }
369
370
        return true;
371
    }
372
373
374
    /**
375
     * Count the number of parameters a function call has been passed.
376
     *
377
     * Expects to be passed the T_STRING stack pointer for the function call.
378
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
379
     *
380
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
381
     * it will return the number of values in the array.
382
     *
383
     * @link https://github.com/wimg/PHPCompatibility/issues/111
384
     * @link https://github.com/wimg/PHPCompatibility/issues/114
385
     * @link https://github.com/wimg/PHPCompatibility/issues/151
386
     *
387
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
388
     * @param int                   $stackPtr  The position of the function call token.
389
     *
390
     * @return int
391
     */
392
    public function getFunctionCallParameterCount(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
393
    {
394
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
395
            return 0;
396
        }
397
398
        return count($this->getFunctionCallParameters($phpcsFile, $stackPtr));
399
    }
400
401
402
    /**
403
     * Get information on all parameters passed to a function call.
404
     *
405
     * Expects to be passed the T_STRING stack pointer for the function call.
406
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
407
     *
408
     * Will return an multi-dimentional array with the start token pointer, end token
409
     * pointer and raw parameter value for all parameters. Index will be 1-based.
410
     * If no parameters are found, will return an empty array.
411
     *
412
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
413
     * it will tokenize the values / key/value pairs contained in the array call.
414
     *
415
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
416
     * @param int                   $stackPtr  The position of the function call token.
417
     *
418
     * @return array
419
     */
420
    public function getFunctionCallParameters(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
421
    {
422
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
423
            return array();
424
        }
425
426
        // Ok, we know we have a T_STRING, T_ARRAY or T_OPEN_SHORT_ARRAY with parameters
427
        // and valid open & close brackets/parenthesis.
428
        $tokens = $phpcsFile->getTokens();
429
430
        // Mark the beginning and end tokens.
431
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) {
432
            $opener = $stackPtr;
433
            $closer = $tokens[$stackPtr]['bracket_closer'];
434
435
            $nestedParenthesisCount = 0;
436
437
        } else {
438
            $opener = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
439
            $closer = $tokens[$opener]['parenthesis_closer'];
440
441
            $nestedParenthesisCount = 1;
442
        }
443
444
        // Which nesting level is the one we are interested in ?
445 View Code Duplication
        if (isset($tokens[$opener]['nested_parenthesis'])) {
446
            $nestedParenthesisCount += count($tokens[$opener]['nested_parenthesis']);
447
        }
448
449
        $parameters = array();
450
        $nextComma  = $opener;
451
        $paramStart = $opener + 1;
452
        $cnt        = 1;
453
        while (($nextComma = $phpcsFile->findNext(array(T_COMMA, $tokens[$closer]['code'], T_OPEN_SHORT_ARRAY), $nextComma + 1, $closer + 1)) !== false) {
454
            // Ignore anything within short array definition brackets.
455 View Code Duplication
            if ($tokens[$nextComma]['type'] === 'T_OPEN_SHORT_ARRAY'
456
                && (isset($tokens[$nextComma]['bracket_opener'])
457
                    && $tokens[$nextComma]['bracket_opener'] === $nextComma)
458
                && isset($tokens[$nextComma]['bracket_closer'])
459
            ) {
460
                // Skip forward to the end of the short array definition.
461
                $nextComma = $tokens[$nextComma]['bracket_closer'];
462
                continue;
463
            }
464
465
            // Ignore comma's at a lower nesting level.
466
            if ($tokens[$nextComma]['type'] === 'T_COMMA'
467
                && isset($tokens[$nextComma]['nested_parenthesis'])
468
                && count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount
469
            ) {
470
                continue;
471
            }
472
473
            // Ignore closing parenthesis/bracket if not 'ours'.
474
            if ($tokens[$nextComma]['type'] === $tokens[$closer]['type'] && $nextComma !== $closer) {
475
                continue;
476
            }
477
478
            // Ok, we've reached the end of the parameter.
479
            $parameters[$cnt]['start'] = $paramStart;
480
            $parameters[$cnt]['end']   = $nextComma - 1;
481
            $parameters[$cnt]['raw']   = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart)));
482
483
            // Check if there are more tokens before the closing parenthesis.
484
            // Prevents code like the following from setting a third parameter:
485
            // functionCall( $param1, $param2, );
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% 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...
486
            $hasNextParam = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $nextComma + 1, $closer, true, null, true);
487
            if ($hasNextParam === false) {
488
                break;
489
            }
490
491
            // Prepare for the next parameter.
492
            $paramStart = $nextComma + 1;
493
            $cnt++;
494
        }
495
496
        return $parameters;
497
    }
498
499
500
    /**
501
     * Get information on a specific parameter passed to a function call.
502
     *
503
     * Expects to be passed the T_STRING stack pointer for the function call.
504
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
505
     *
506
     * Will return a array with the start token pointer, end token pointer and the raw value
507
     * of the parameter at a specific offset.
508
     * If the specified parameter is not found, will return false.
509
     *
510
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
511
     * @param int                   $stackPtr    The position of the function call token.
512
     * @param int                   $paramOffset The 1-based index position of the parameter to retrieve.
513
     *
514
     * @return array|false
515
     */
516
    public function getFunctionCallParameter(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $paramOffset)
517
    {
518
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
519
520
        if (isset($parameters[$paramOffset]) === false) {
521
            return false;
522
        } else {
523
            return $parameters[$paramOffset];
524
        }
525
    }
526
527
528
    /**
529
     * Verify whether a token is within a scoped condition.
530
     *
531
     * If the optional $validScopes parameter has been passed, the function
532
     * will check that the token has at least one condition which is of a
533
     * type defined in $validScopes.
534
     *
535
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
536
     * @param int                   $stackPtr    The position of the token.
537
     * @param array|int             $validScopes Optional. Array of valid scopes
538
     *                                           or int value of a valid scope.
539
     *                                           Pass the T_.. constant(s) for the
540
     *                                           desired scope to this parameter.
541
     *
542
     * @return bool Without the optional $scopeTypes: True if within a scope, false otherwise.
543
     *              If the $scopeTypes are set: True if *one* of the conditions is a
544
     *              valid scope, false otherwise.
545
     */
546
    public function tokenHasScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $validScopes = null)
547
    {
548
        $tokens = $phpcsFile->getTokens();
549
550
        // Check for the existence of the token.
551
        if (isset($tokens[$stackPtr]) === false) {
552
            return false;
553
        }
554
555
        // No conditions = no scope.
556
        if (empty($tokens[$stackPtr]['conditions'])) {
557
            return false;
558
        }
559
560
        // Ok, there are conditions, do we have to check for specific ones ?
561
        if (isset($validScopes) === false) {
562
            return true;
563
        }
564
565
        return $phpcsFile->hasCondition($stackPtr, $validScopes);
566
    }
567
568
569
    /**
570
     * Verify whether a token is within a class scope.
571
     *
572
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
573
     * @param int                   $stackPtr  The position of the token.
574
     * @param bool                  $strict    Whether to strictly check for the T_CLASS
575
     *                                         scope or also accept interfaces and traits
576
     *                                         as scope.
577
     *
578
     * @return bool True if within class scope, false otherwise.
579
     */
580
    public function inClassScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $strict = true)
581
    {
582
        $validScopes = array(T_CLASS);
583
        if (defined('T_ANON_CLASS') === true) {
584
            $validScopes[] = T_ANON_CLASS;
585
        }
586
587
        if ($strict === false) {
588
            $validScopes[] = T_INTERFACE;
589
590
            if (defined('T_TRAIT')) {
591
                // phpcs:ignore PHPCompatibility.PHP.NewConstants.t_traitFound
592
                $validScopes[] = T_TRAIT;
593
            }
594
        }
595
596
        return $phpcsFile->hasCondition($stackPtr, $validScopes);
597
    }
598
599
600
    /**
601
     * Verify whether a token is within a scoped use statement.
602
     *
603
     * PHPCS cross-version compatibility method.
604
     *
605
     * In PHPCS 1.x no conditions are set for a scoped use statement.
606
     * This method works around that limitation.
607
     *
608
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
609
     * @param int                   $stackPtr  The position of the token.
610
     *
611
     * @return bool True if within use scope, false otherwise.
612
     */
613
    public function inUseScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
614
    {
615
        static $isLowPHPCS, $ignoreTokens;
616
617
        if (isset($isLowPHPCS) === false) {
618
            $isLowPHPCS = version_compare(PHPCSHelper::getVersion(), '2.3.0', '<');
619
        }
620
        if (isset($ignoreTokens) === false) {
621
            $ignoreTokens              = \PHP_CodeSniffer_Tokens::$emptyTokens;
622
            $ignoreTokens[T_STRING]    = T_STRING;
623
            $ignoreTokens[T_AS]        = T_AS;
624
            $ignoreTokens[T_PUBLIC]    = T_PUBLIC;
625
            $ignoreTokens[T_PROTECTED] = T_PROTECTED;
626
            $ignoreTokens[T_PRIVATE]   = T_PRIVATE;
627
        }
628
629
        // PHPCS 2.0.
630
        if ($isLowPHPCS === false) {
631
            return $phpcsFile->hasCondition($stackPtr, T_USE);
632
        } else {
633
            // PHPCS 1.x.
634
            $tokens         = $phpcsFile->getTokens();
635
            $maybeCurlyOpen = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
636
            if ($tokens[$maybeCurlyOpen]['code'] === T_OPEN_CURLY_BRACKET) {
637
                $maybeUseStatement = $phpcsFile->findPrevious($ignoreTokens, ($maybeCurlyOpen - 1), null, true);
638
                if ($tokens[$maybeUseStatement]['code'] === T_USE) {
639
                    return true;
640
                }
641
            }
642
            return false;
643
        }
644
    }
645
646
647
    /**
648
     * Returns the fully qualified class name for a new class instantiation.
649
     *
650
     * Returns an empty string if the class name could not be reliably inferred.
651
     *
652
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
653
     * @param int                   $stackPtr  The position of a T_NEW token.
654
     *
655
     * @return string
656
     */
657
    public function getFQClassNameFromNewToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
658
    {
659
        $tokens = $phpcsFile->getTokens();
660
661
        // Check for the existence of the token.
662
        if (isset($tokens[$stackPtr]) === false) {
663
            return '';
664
        }
665
666
        if ($tokens[$stackPtr]['code'] !== T_NEW) {
667
            return '';
668
        }
669
670
        $start = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
671
        if ($start === false) {
672
            return '';
673
        }
674
675
        // Bow out if the next token is a variable as we don't know where it was defined.
676
        if ($tokens[$start]['code'] === T_VARIABLE) {
677
            return '';
678
        }
679
680
        // Bow out if the next token is the class keyword.
681
        if ($tokens[$start]['type'] === 'T_ANON_CLASS' || $tokens[$start]['code'] === T_CLASS) {
682
            return '';
683
        }
684
685
        $find = array(
686
            T_NS_SEPARATOR,
687
            T_STRING,
688
            T_NAMESPACE,
689
            T_WHITESPACE,
690
        );
691
692
        $end       = $phpcsFile->findNext($find, ($start + 1), null, true, null, true);
693
        $className = $phpcsFile->getTokensAsString($start, ($end - $start));
694
        $className = trim($className);
695
696
        return $this->getFQName($phpcsFile, $stackPtr, $className);
697
    }
698
699
700
    /**
701
     * Returns the fully qualified name of the class that the specified class extends.
702
     *
703
     * Returns an empty string if the class does not extend another class or if
704
     * the class name could not be reliably inferred.
705
     *
706
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
707
     * @param int                   $stackPtr  The position of a T_CLASS token.
708
     *
709
     * @return string
710
     */
711
    public function getFQExtendedClassName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
712
    {
713
        $tokens = $phpcsFile->getTokens();
714
715
        // Check for the existence of the token.
716
        if (isset($tokens[$stackPtr]) === false) {
717
            return '';
718
        }
719
720 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_CLASS
721
            && $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
722
            && $tokens[$stackPtr]['type'] !== 'T_INTERFACE'
723
        ) {
724
            return '';
725
        }
726
727
        $extends = PHPCSHelper::findExtendedClassName($phpcsFile, $stackPtr);
728
        if (empty($extends) || is_string($extends) === false) {
729
            return '';
730
        }
731
732
        return $this->getFQName($phpcsFile, $stackPtr, $extends);
733
    }
734
735
736
    /**
737
     * Returns the class name for the static usage of a class.
738
     * This can be a call to a method, the use of a property or constant.
739
     *
740
     * Returns an empty string if the class name could not be reliably inferred.
741
     *
742
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
743
     * @param int                   $stackPtr  The position of a T_NEW token.
744
     *
745
     * @return string
746
     */
747
    public function getFQClassNameFromDoubleColonToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
748
    {
749
        $tokens = $phpcsFile->getTokens();
750
751
        // Check for the existence of the token.
752
        if (isset($tokens[$stackPtr]) === false) {
753
            return '';
754
        }
755
756
        if ($tokens[$stackPtr]['code'] !== T_DOUBLE_COLON) {
757
            return '';
758
        }
759
760
        // Nothing to do if previous token is a variable as we don't know where it was defined.
761
        if ($tokens[$stackPtr - 1]['code'] === T_VARIABLE) {
762
            return '';
763
        }
764
765
        // Nothing to do if 'parent' or 'static' as we don't know how far the class tree extends.
766
        if (in_array($tokens[$stackPtr - 1]['code'], array(T_PARENT, T_STATIC), true)) {
767
            return '';
768
        }
769
770
        // Get the classname from the class declaration if self is used.
771
        if ($tokens[$stackPtr - 1]['code'] === T_SELF) {
772
            $classDeclarationPtr = $phpcsFile->findPrevious(T_CLASS, $stackPtr - 1);
773
            if ($classDeclarationPtr === false) {
774
                return '';
775
            }
776
            $className = $phpcsFile->getDeclarationName($classDeclarationPtr);
777
            return $this->getFQName($phpcsFile, $classDeclarationPtr, $className);
778
        }
779
780
        $find = array(
781
            T_NS_SEPARATOR,
782
            T_STRING,
783
            T_NAMESPACE,
784
            T_WHITESPACE,
785
        );
786
787
        $start = $phpcsFile->findPrevious($find, $stackPtr - 1, null, true, null, true);
788
        if ($start === false || isset($tokens[($start + 1)]) === false) {
789
            return '';
790
        }
791
792
        $start     = ($start + 1);
793
        $className = $phpcsFile->getTokensAsString($start, ($stackPtr - $start));
794
        $className = trim($className);
795
796
        return $this->getFQName($phpcsFile, $stackPtr, $className);
797
    }
798
799
800
    /**
801
     * Get the Fully Qualified name for a class/function/constant etc.
802
     *
803
     * Checks if a class/function/constant name is already fully qualified and
804
     * if not, enrich it with the relevant namespace information.
805
     *
806
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
807
     * @param int                   $stackPtr  The position of the token.
808
     * @param string                $name      The class / function / constant name.
809
     *
810
     * @return string
811
     */
812
    public function getFQName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $name)
813
    {
814
        if (strpos($name, '\\') === 0) {
815
            // Already fully qualified.
816
            return $name;
817
        }
818
819
        // Remove the namespace keyword if used.
820
        if (strpos($name, 'namespace\\') === 0) {
821
            $name = substr($name, 10);
822
        }
823
824
        $namespace = $this->determineNamespace($phpcsFile, $stackPtr);
825
826
        if ($namespace === '') {
827
            return '\\' . $name;
828
        } else {
829
            return '\\' . $namespace . '\\' . $name;
830
        }
831
    }
832
833
834
    /**
835
     * Is the class/function/constant name namespaced or global ?
836
     *
837
     * @param string $FQName Fully Qualified name of a class, function etc.
838
     *                       I.e. should always start with a `\`.
839
     *
840
     * @return bool True if namespaced, false if global.
841
     */
842
    public function isNamespaced($FQName)
843
    {
844
        if (strpos($FQName, '\\') !== 0) {
845
            throw new \PHP_CodeSniffer_Exception('$FQName must be a fully qualified name');
846
        }
847
848
        return (strpos(substr($FQName, 1), '\\') !== false);
849
    }
850
851
852
    /**
853
     * Determine the namespace name an arbitrary token lives in.
854
     *
855
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
856
     * @param int                   $stackPtr  The token position for which to determine the namespace.
857
     *
858
     * @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
859
     */
860
    public function determineNamespace(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
861
    {
862
        $tokens = $phpcsFile->getTokens();
863
864
        // Check for the existence of the token.
865
        if (isset($tokens[$stackPtr]) === false) {
866
            return '';
867
        }
868
869
        // Check for scoped namespace {}.
870
        if (empty($tokens[$stackPtr]['conditions']) === false) {
871
            $namespacePtr = $phpcsFile->getCondition($stackPtr, T_NAMESPACE);
872
            if ($namespacePtr !== false) {
873
                $namespace = $this->getDeclaredNamespaceName($phpcsFile, $namespacePtr);
874
                if ($namespace !== false) {
875
                    return $namespace;
876
                }
877
878
                // We are in a scoped namespace, but couldn't determine the name. Searching for a global namespace is futile.
879
                return '';
880
            }
881
        }
882
883
        /*
884
         * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
885
         * Keeping in mind that:
886
         * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
887
         * - the namespace keyword can also be used as part of a function/method call and such.
888
         * - that a non-named namespace resolves to the global namespace.
889
         */
890
        $previousNSToken = $stackPtr;
891
        $namespace       = false;
892
        do {
893
            $previousNSToken = $phpcsFile->findPrevious(T_NAMESPACE, ($previousNSToken - 1));
894
895
            // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
896
            if (empty($tokens[$previousNSToken]['scope_condition']) === false && $tokens[$previousNSToken]['scope_condition'] === $previousNSToken) {
897
                break;
898
            }
899
900
            $namespace = $this->getDeclaredNamespaceName($phpcsFile, $previousNSToken);
901
902
        } while ($namespace === false && $previousNSToken !== false);
903
904
        // If we still haven't got a namespace, return an empty string.
905
        if ($namespace === false) {
906
            return '';
907
        } else {
908
            return $namespace;
909
        }
910
    }
911
912
    /**
913
     * Get the complete namespace name for a namespace declaration.
914
     *
915
     * For hierarchical namespaces, the name will be composed of several tokens,
916
     * i.e. MyProject\Sub\Level which will be returned together as one string.
917
     *
918
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
919
     * @param int|bool              $stackPtr  The position of a T_NAMESPACE token.
920
     *
921
     * @return string|false Namespace name or false if not a namespace declaration.
922
     *                      Namespace name can be an empty string for global namespace declaration.
923
     */
924
    public function getDeclaredNamespaceName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
925
    {
926
        $tokens = $phpcsFile->getTokens();
927
928
        // Check for the existence of the token.
929 View Code Duplication
        if ($stackPtr === false || isset($tokens[$stackPtr]) === false) {
930
            return false;
931
        }
932
933
        if ($tokens[$stackPtr]['code'] !== T_NAMESPACE) {
934
            return false;
935
        }
936
937
        if ($tokens[($stackPtr + 1)]['code'] === T_NS_SEPARATOR) {
938
            // Not a namespace declaration, but use of, i.e. namespace\someFunction();
939
            return false;
940
        }
941
942
        $nextToken = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
943
        if ($tokens[$nextToken]['code'] === T_OPEN_CURLY_BRACKET) {
944
            // Declaration for global namespace when using multiple namespaces in a file.
945
            // I.e.: namespace {}
946
            return '';
947
        }
948
949
        // Ok, this should be a namespace declaration, so get all the parts together.
950
        $validTokens = array(
951
            T_STRING       => true,
952
            T_NS_SEPARATOR => true,
953
            T_WHITESPACE   => true,
954
        );
955
956
        $namespaceName = '';
957
        while (isset($validTokens[$tokens[$nextToken]['code']]) === true) {
958
            $namespaceName .= trim($tokens[$nextToken]['content']);
959
            $nextToken++;
960
        }
961
962
        return $namespaceName;
963
    }
964
965
966
    /**
967
     * Get the stack pointer for a return type token for a given function.
968
     *
969
     * Compatible layer for older PHPCS versions which don't recognize
970
     * return type hints correctly.
971
     *
972
     * Expects to be passed T_RETURN_TYPE, T_FUNCTION or T_CLOSURE token.
973
     *
974
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
975
     * @param int                   $stackPtr  The position of the token.
976
     *
977
     * @return int|false Stack pointer to the return type token or false if
978
     *                   no return type was found or the passed token was
979
     *                   not of the correct type.
980
     */
981
    public function getReturnTypeHintToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
982
    {
983
        $tokens = $phpcsFile->getTokens();
984
985
        if (defined('T_RETURN_TYPE') && $tokens[$stackPtr]['code'] === T_RETURN_TYPE) {
986
            return $stackPtr;
987
        }
988
989 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION && $tokens[$stackPtr]['code'] !== T_CLOSURE) {
990
            return false;
991
        }
992
993
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === false) {
994
            return false;
995
        }
996
997
        // Allow for interface and abstract method declarations.
998
        $endOfFunctionDeclaration = null;
999
        if (isset($tokens[$stackPtr]['scope_opener'])) {
1000
            $endOfFunctionDeclaration = $tokens[$stackPtr]['scope_opener'];
1001
        } else {
1002
            $nextSemiColon = $phpcsFile->findNext(T_SEMICOLON, ($tokens[$stackPtr]['parenthesis_closer'] + 1), null, false, null, true);
1003
            if ($nextSemiColon !== false) {
1004
                $endOfFunctionDeclaration = $nextSemiColon;
1005
            }
1006
        }
1007
1008
        if (isset($endOfFunctionDeclaration) === false) {
1009
            return false;
1010
        }
1011
1012
        $hasColon = $phpcsFile->findNext(
1013
            array(T_COLON, T_INLINE_ELSE),
1014
            ($tokens[$stackPtr]['parenthesis_closer'] + 1),
1015
            $endOfFunctionDeclaration
1016
        );
1017
        if ($hasColon === false) {
1018
            return false;
1019
        }
1020
1021
        /*
1022
         * - `self`, `parent` and `callable` are not being recognized as return types in PHPCS < 2.6.0.
1023
         * - Return types are not recognized at all in PHPCS < 2.4.0.
1024
         * - The T_RETURN_TYPE token is defined, but no longer in use since PHPCS 3.3.0+.
1025
         *   The token will now be tokenized as T_STRING.
1026
         * - An `array` (return) type declaration was tokenized as `T_ARRAY_HINT` in PHPCS 2.3.3 - 3.2.3
1027
         *   to prevent confusing sniffs looking for array declarations.
1028
         *   As of PHPCS 3.3.0 `array` as a type declaration will be tokenized as `T_STRING`.
1029
         */
1030
        $unrecognizedTypes = array(
1031
            T_CALLABLE,
1032
            T_SELF,
1033
            T_PARENT,
1034
            T_ARRAY, // PHPCS < 2.4.0.
1035
            T_STRING,
1036
        );
1037
1038
        return $phpcsFile->findPrevious($unrecognizedTypes, ($endOfFunctionDeclaration - 1), $hasColon);
1039
    }
1040
1041
1042
    /**
1043
     * Get the complete return type declaration for a given function.
1044
     *
1045
     * Cross-version compatible way to retrieve the complete return type declaration.
1046
     *
1047
     * For a classname-based return type, PHPCS, as well as the Sniff::getReturnTypeHintToken()
1048
     * method will mark the classname as the return type token.
1049
     * This method will find preceeding namespaces and namespace separators and will return a
1050
     * string containing the qualified return type declaration.
1051
     *
1052
     * Expects to be passed a T_RETURN_TYPE token or the return value from a call to
1053
     * the Sniff::getReturnTypeHintToken() method.
1054
     *
1055
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1056
     * @param int                   $stackPtr  The position of the return type token.
1057
     *
1058
     * @return string|false The name of the return type token.
1059
     */
1060
    public function getReturnTypeHintName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1061
    {
1062
        $tokens = $phpcsFile->getTokens();
1063
1064
        // In older PHPCS versions, the nullable indicator will turn a return type colon into a T_INLINE_ELSE.
1065
        $colon = $phpcsFile->findPrevious(array(T_COLON, T_INLINE_ELSE, T_FUNCTION, T_CLOSE_PARENTHESIS), ($stackPtr - 1));
1066
        if ($colon === false
1067
            || ($tokens[$colon]['code'] !== T_COLON && $tokens[$colon]['code'] !== T_INLINE_ELSE)
1068
        ) {
1069
            // Shouldn't happen, just in case.
1070
            return;
1071
        }
1072
1073
        $emptyTokens = array_flip(\PHP_CodeSniffer_Tokens::$emptyTokens); // PHPCS 1.x compat.
1074
1075
        $returnTypeHint = '';
1076
        for ($i = ($colon + 1); $i <= $stackPtr; $i++) {
1077
            // As of PHPCS 3.3.0+, all tokens are tokenized as "normal", so T_CALLABLE, T_SELF etc are
1078
            // all possible, just exclude anything that's regarded as empty and the nullable indicator.
1079
            if (isset($emptyTokens[$tokens[$i]['code']])) {
1080
                continue;
1081
            }
1082
1083
            if ($tokens[$i]['type'] === 'T_NULLABLE') {
1084
                continue;
1085
            }
1086
1087
            if (defined('T_NULLABLE') === false && $tokens[$i]['code'] === T_INLINE_THEN) {
1088
                // Old PHPCS.
1089
                continue;
1090
            }
1091
1092
            $returnTypeHint .= $tokens[$i]['content'];
1093
        }
1094
1095
        return $returnTypeHint;
1096
    }
1097
1098
1099
    /**
1100
     * Check whether a T_VARIABLE token is a class property declaration.
1101
     *
1102
     * Compatibility layer for PHPCS cross-version compatibility
1103
     * as PHPCS 2.4.0 - 2.7.1 does not have good enough support for
1104
     * anonymous classes. Along the same lines, the`getMemberProperties()`
1105
     * method does not support the `var` prefix.
1106
     *
1107
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1108
     * @param int                   $stackPtr  The position in the stack of the
1109
     *                                         T_VARIABLE token to verify.
1110
     *
1111
     * @return bool
1112
     */
1113
    public function isClassProperty(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1114
    {
1115
        $tokens = $phpcsFile->getTokens();
1116
1117 View Code Duplication
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_VARIABLE) {
1118
            return false;
1119
        }
1120
1121
        // Note: interfaces can not declare properties.
1122
        $validScopes = array(
1123
            'T_CLASS'      => true,
1124
            'T_ANON_CLASS' => true,
1125
            'T_TRAIT'      => true,
1126
        );
1127
        if ($this->validDirectScope($phpcsFile, $stackPtr, $validScopes) === true) {
1128
            // Make sure it's not a method parameter.
1129
            if (empty($tokens[$stackPtr]['nested_parenthesis']) === true) {
1130
                return true;
1131
            }
1132
        }
1133
1134
        return false;
1135
    }
1136
1137
1138
    /**
1139
     * Check whether a T_CONST token is a class constant declaration.
1140
     *
1141
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1142
     * @param int                   $stackPtr  The position in the stack of the
1143
     *                                         T_CONST token to verify.
1144
     *
1145
     * @return bool
1146
     */
1147
    public function isClassConstant(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1148
    {
1149
        $tokens = $phpcsFile->getTokens();
1150
1151 View Code Duplication
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_CONST) {
1152
            return false;
1153
        }
1154
1155
        // Note: traits can not declare constants.
1156
        $validScopes = array(
1157
            'T_CLASS'      => true,
1158
            'T_ANON_CLASS' => true,
1159
            'T_INTERFACE'  => true,
1160
        );
1161
        if ($this->validDirectScope($phpcsFile, $stackPtr, $validScopes) === true) {
1162
            return true;
1163
        }
1164
1165
        return false;
1166
    }
1167
1168
1169
    /**
1170
     * Check whether the direct wrapping scope of a token is within a limited set of
1171
     * acceptable tokens.
1172
     *
1173
     * Used to check, for instance, if a T_CONST is a class constant.
1174
     *
1175
     * @param \PHP_CodeSniffer_File $phpcsFile   Instance of phpcsFile.
1176
     * @param int                   $stackPtr    The position in the stack of the
1177
     *                                           T_CONST token to verify.
1178
     * @param array                 $validScopes Array of token types.
1179
     *                                           Keys should be the token types in string
1180
     *                                           format to allow for newer token types.
1181
     *                                           Value is irrelevant.
1182
     *
1183
     * @return bool
1184
     */
1185
    protected function validDirectScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $validScopes)
1186
    {
1187
        $tokens = $phpcsFile->getTokens();
1188
1189
        if (empty($tokens[$stackPtr]['conditions']) === true) {
1190
            return false;
1191
        }
1192
1193
        /*
1194
         * Check only the direct wrapping scope of the token.
1195
         */
1196
        $conditions = array_keys($tokens[$stackPtr]['conditions']);
1197
        $ptr        = array_pop($conditions);
1198
1199
        if (isset($tokens[$ptr]) === false) {
1200
            return false;
1201
        }
1202
1203
        if (isset($validScopes[$tokens[$ptr]['type']]) === true) {
1204
            return true;
1205
        }
1206
1207
        return false;
1208
    }
1209
1210
1211
    /**
1212
     * Get an array of just the type hints from a function declaration.
1213
     *
1214
     * Expects to be passed T_FUNCTION or T_CLOSURE token.
1215
     *
1216
     * Strips potential nullable indicator and potential global namespace
1217
     * indicator from the type hints before returning them.
1218
     *
1219
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1220
     * @param int                   $stackPtr  The position of the token.
1221
     *
1222
     * @return array Array with type hints or an empty array if
1223
     *               - the function does not have any parameters
1224
     *               - no type hints were found
1225
     *               - or the passed token was not of the correct type.
1226
     */
1227
    public function getTypeHintsFromFunctionDeclaration(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1228
    {
1229
        $tokens = $phpcsFile->getTokens();
1230
1231 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION && $tokens[$stackPtr]['code'] !== T_CLOSURE) {
1232
            return array();
1233
        }
1234
1235
        $parameters = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
1236
        if (empty($parameters) || is_array($parameters) === false) {
1237
            return array();
1238
        }
1239
1240
        $typeHints = array();
1241
1242
        foreach ($parameters as $param) {
1243
            if ($param['type_hint'] === '') {
1244
                continue;
1245
            }
1246
1247
            // Strip off potential nullable indication.
1248
            $typeHint = ltrim($param['type_hint'], '?');
1249
1250
            // Strip off potential (global) namespace indication.
1251
            $typeHint = ltrim($typeHint, '\\');
1252
1253
            if ($typeHint !== '') {
1254
                $typeHints[] = $typeHint;
1255
            }
1256
        }
1257
1258
        return $typeHints;
1259
    }
1260
1261
1262
    /**
1263
     * Get the hash algorithm name from the parameter in a hash function call.
1264
     *
1265
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1266
     * @param int                   $stackPtr  The position of the T_STRING function token.
1267
     *
1268
     * @return string|false The algorithm name without quotes if this was a relevant hash
1269
     *                      function call or false if it was not.
1270
     */
1271
    public function getHashAlgorithmParameter(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1272
    {
1273
        $tokens = $phpcsFile->getTokens();
1274
1275
        // Check for the existence of the token.
1276
        if (isset($tokens[$stackPtr]) === false) {
1277
            return false;
1278
        }
1279
1280
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
1281
            return false;
1282
        }
1283
1284
        $functionName   = $tokens[$stackPtr]['content'];
1285
        $functionNameLc = strtolower($functionName);
1286
1287
        // Bow out if not one of the functions we're targetting.
1288
        if (isset($this->hashAlgoFunctions[$functionNameLc]) === false) {
1289
            return false;
1290
        }
1291
1292
        // Get the parameter from the function call which should contain the algorithm name.
1293
        $algoParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->hashAlgoFunctions[$functionNameLc]);
1294
        if ($algoParam === false) {
1295
            return false;
1296
        }
1297
1298
        // Algorithm is a text string, so we need to remove the quotes.
1299
        $algo = strtolower(trim($algoParam['raw']));
1300
        $algo = $this->stripQuotes($algo);
1301
1302
        return $algo;
1303
    }
1304
1305
1306
    /**
1307
     * Determine whether an arbitrary T_STRING token is the use of a global constant.
1308
     *
1309
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1310
     * @param int                   $stackPtr  The position of the function call token.
1311
     *
1312
     * @return bool
1313
     */
1314
    public function isUseOfGlobalConstant(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1315
    {
1316
        static $isLowPHPCS, $isLowPHP;
1317
1318
        $tokens = $phpcsFile->getTokens();
1319
1320
        // Check for the existence of the token.
1321
        if (isset($tokens[$stackPtr]) === false) {
1322
            return false;
1323
        }
1324
1325
        // Is this one of the tokens this function handles ?
1326
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
1327
            return false;
1328
        }
1329
1330
        // Check for older PHP, PHPCS version so we can compensate for misidentified tokens.
1331
        if (isset($isLowPHPCS, $isLowPHP) === false) {
1332
            $isLowPHP   = false;
1333
            $isLowPHPCS = false;
1334
            if (version_compare(PHP_VERSION_ID, '50400', '<')) {
1335
                $isLowPHP   = true;
1336
                $isLowPHPCS = version_compare(PHPCSHelper::getVersion(), '2.4.0', '<');
1337
            }
1338
        }
1339
1340
        $next = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
1341
        if ($next !== false
1342
            && ($tokens[$next]['code'] === T_OPEN_PARENTHESIS
1343
                || $tokens[$next]['code'] === T_DOUBLE_COLON)
1344
        ) {
1345
            // Function call or declaration.
1346
            return false;
1347
        }
1348
1349
        // Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a global constant.
1350
        $tokensToIgnore = array(
1351
            'T_NAMESPACE'       => true,
1352
            'T_USE'             => true,
1353
            'T_CLASS'           => true,
1354
            'T_TRAIT'           => true,
1355
            'T_INTERFACE'       => true,
1356
            'T_EXTENDS'         => true,
1357
            'T_IMPLEMENTS'      => true,
1358
            'T_NEW'             => true,
1359
            'T_FUNCTION'        => true,
1360
            'T_DOUBLE_COLON'    => true,
1361
            'T_OBJECT_OPERATOR' => true,
1362
            'T_INSTANCEOF'      => true,
1363
            'T_INSTEADOF'       => true,
1364
            'T_GOTO'            => true,
1365
            'T_AS'              => true,
1366
            'T_PUBLIC'          => true,
1367
            'T_PROTECTED'       => true,
1368
            'T_PRIVATE'         => true,
1369
        );
1370
1371
        $prev = $phpcsFile->findPrevious(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
1372
        if ($prev !== false
1373
            && (isset($tokensToIgnore[$tokens[$prev]['type']]) === true
1374
                || ($tokens[$prev]['code'] === T_STRING
1375
                    && (($isLowPHPCS === true
1376
                        && $tokens[$prev]['content'] === 'trait')
1377
                    || ($isLowPHP === true
1378
                        && $tokens[$prev]['content'] === 'insteadof'))))
1379
        ) {
1380
            // Not the use of a constant.
1381
            return false;
1382
        }
1383
1384
        if ($prev !== false
1385
            && $tokens[$prev]['code'] === T_NS_SEPARATOR
1386
            && $tokens[($prev - 1)]['code'] === T_STRING
1387
        ) {
1388
            // Namespaced constant of the same name.
1389
            return false;
1390
        }
1391
1392
        if ($prev !== false
1393
            && $tokens[$prev]['code'] === T_CONST
1394
            && $this->isClassConstant($phpcsFile, $prev) === true
1395
        ) {
1396
            // Class constant declaration of the same name.
1397
            return false;
1398
        }
1399
1400
        /*
1401
         * Deal with a number of variations of use statements.
1402
         */
1403
        for ($i = $stackPtr; $i > 0; $i--) {
1404
            if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
1405
                break;
1406
            }
1407
        }
1408
1409
        $firstOnLine = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true);
1410
        if ($firstOnLine !== false && $tokens[$firstOnLine]['code'] === T_USE) {
1411
            $nextOnLine = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($firstOnLine + 1), null, true);
1412
            if ($nextOnLine !== false) {
1413
                if (($tokens[$nextOnLine]['code'] === T_STRING && $tokens[$nextOnLine]['content'] === 'const')
1414
                    || $tokens[$nextOnLine]['code'] === T_CONST // Happens in some PHPCS versions.
1415
                ) {
1416
                    $hasNsSep = $phpcsFile->findNext(T_NS_SEPARATOR, ($nextOnLine + 1), $stackPtr);
1417
                    if ($hasNsSep !== false) {
1418
                        // Namespaced const (group) use statement.
1419
                        return false;
1420
                    }
1421
                } else {
1422
                    // Not a const use statement.
1423
                    return false;
1424
                }
1425
            }
1426
        }
1427
1428
        return true;
1429
    }
1430
1431
1432
    /**
1433
     * Determine whether the tokens between $start and $end together form a positive number
1434
     * as recognized by PHP.
1435
     *
1436
     * The outcome of this function is reliable for `true`, `false` should be regarded as
1437
     * "undetermined".
1438
     *
1439
     * Note: Zero is *not* regarded as a positive number.
1440
     *
1441
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1442
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1443
     *                                           token will be examined as part of the snippet.
1444
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1445
     *                                           token will be examined as part of the snippet.
1446
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1447
     *
1448
     * @return bool True if PHP would evaluate the snippet as a positive number.
1449
     *              False if not or if it could not be reliably determined
1450
     *              (variable or calculations and such).
1451
     */
1452 View Code Duplication
    public function isPositiveNumber(\PHP_CodeSniffer_File $phpcsFile, $start, $end, $allowFloats = false)
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...
1453
    {
1454
        $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
1455
1456
        if ($number === false) {
1457
            return false;
1458
        }
1459
1460
        return ($number > 0);
1461
    }
1462
1463
1464
    /**
1465
     * Determine whether the tokens between $start and $end together form a negative number
1466
     * as recognized by PHP.
1467
     *
1468
     * The outcome of this function is reliable for `true`, `false` should be regarded as
1469
     * "undetermined".
1470
     *
1471
     * Note: Zero is *not* regarded as a negative number.
1472
     *
1473
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1474
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1475
     *                                           token will be examined as part of the snippet.
1476
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1477
     *                                           token will be examined as part of the snippet.
1478
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1479
     *
1480
     * @return bool True if PHP would evaluate the snippet as a negative number.
1481
     *              False if not or if it could not be reliably determined
1482
     *              (variable or calculations and such).
1483
     */
1484 View Code Duplication
    public function isNegativeNumber(\PHP_CodeSniffer_File $phpcsFile, $start, $end, $allowFloats = false)
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...
1485
    {
1486
        $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
1487
1488
        if ($number === false) {
1489
            return false;
1490
        }
1491
1492
        return ($number < 0);
1493
1494
    }
1495
1496
    /**
1497
     * Determine whether the tokens between $start and $end together form a number
1498
     * as recognized by PHP.
1499
     *
1500
     * The outcome of this function is reliable for "true-ish" values, `false` should
1501
     * be regarded as "undetermined".
1502
     *
1503
     * @link https://3v4l.org/npTeM
1504
     *
1505
     * Mainly intended for examining variable assignments, function call parameters, array values
1506
     * where the start and end of the snippet to examine is very clear.
1507
     *
1508
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1509
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1510
     *                                           token will be examined as part of the snippet.
1511
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1512
     *                                           token will be examined as part of the snippet.
1513
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1514
     *
1515
     * @return int|float|bool The number found if PHP would evaluate the snippet as a number.
1516
     *                        The return type will be int if $allowFloats is false, if
1517
     *                        $allowFloats is true, the return type will be float.
1518
     *                        False will be returned when the snippet does not evaluate to a
1519
     *                        number or if it could not be reliably determined
1520
     *                        (variable or calculations and such).
1521
     */
1522
    protected function isNumber(\PHP_CodeSniffer_File $phpcsFile, $start, $end, $allowFloats = false)
1523
    {
1524
        $stringTokens  = array_flip(\PHP_CodeSniffer_Tokens::$heredocTokens); // Flipping for PHPCS 1.x compat.
1525
        $stringTokens += array_flip(\PHP_CodeSniffer_Tokens::$stringTokens); // Flipping for PHPCS 1.x compat.
1526
1527
        $validTokens            = array();
1528
        $validTokens[T_LNUMBER] = true;
1529
        $validTokens[T_TRUE]    = true; // Evaluates to int 1.
1530
        $validTokens[T_FALSE]   = true; // Evaluates to int 0.
1531
        $validTokens[T_NULL]    = true; // Evaluates to int 0.
1532
1533
        if ($allowFloats === true) {
1534
            $validTokens[T_DNUMBER] = true;
1535
        }
1536
1537
        $maybeValidTokens = $stringTokens + $validTokens;
1538
1539
        $tokens         = $phpcsFile->getTokens();
1540
        $searchEnd      = ($end + 1);
1541
        $negativeNumber = false;
1542
1543
        if (isset($tokens[$start], $tokens[$searchEnd]) === false) {
1544
            return false;
1545
        }
1546
1547
        $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $start, $searchEnd, true);
1548
        while ($nextNonEmpty !== false
1549
            && ($tokens[$nextNonEmpty]['code'] === T_PLUS
1550
            || $tokens[$nextNonEmpty]['code'] === T_MINUS)
1551
        ) {
1552
1553
            if ($tokens[$nextNonEmpty]['code'] === T_MINUS) {
1554
                $negativeNumber = ($negativeNumber === false ) ? true : false;
1555
            }
1556
1557
            $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
1558
        }
1559
1560
        if ($nextNonEmpty === false || isset($maybeValidTokens[$tokens[$nextNonEmpty]['code']]) === false) {
1561
            return false;
1562
        }
1563
1564
        $content = false;
1565
        if ($tokens[$nextNonEmpty]['code'] === T_LNUMBER
1566
            || $tokens[$nextNonEmpty]['code'] === T_DNUMBER
1567
        ) {
1568
            $content = (float) $tokens[$nextNonEmpty]['content'];
1569
        } elseif ($tokens[$nextNonEmpty]['code'] === T_TRUE) {
1570
            $content = 1.0;
1571
        } elseif ($tokens[$nextNonEmpty]['code'] === T_FALSE
1572
            || $tokens[$nextNonEmpty]['code'] === T_NULL
1573
        ) {
1574
            $content = 0.0;
1575
        } elseif (isset($stringTokens[$tokens[$nextNonEmpty]['code']]) === true) {
1576
1577
            if ($tokens[$nextNonEmpty]['code'] === T_START_HEREDOC
1578
                || $tokens[$nextNonEmpty]['code'] === T_START_NOWDOC
1579
            ) {
1580
                // Skip past heredoc/nowdoc opener to the first content.
1581
                $firstDocToken = $phpcsFile->findNext(array(T_HEREDOC, T_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
1582
                if ($firstDocToken === false) {
1583
                    // Live coding or parse error.
1584
                    return false;
1585
                }
1586
1587
                $stringContent = $content = $tokens[$firstDocToken]['content'];
1588
1589
                // Skip forward to the end in preparation for the next part of the examination.
1590
                $nextNonEmpty = $phpcsFile->findNext(array(T_END_HEREDOC, T_END_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
1591
                if ($nextNonEmpty === false) {
1592
                    // Live coding or parse error.
1593
                    return false;
1594
                }
1595
            } else {
1596
                // Gather subsequent lines for a multi-line string.
1597
                for ($i = $nextNonEmpty; $i < $searchEnd; $i++) {
1598
                    if ($tokens[$i]['code'] !== $tokens[$nextNonEmpty]['code']) {
1599
                        break;
1600
                    }
1601
                    $content .= $tokens[$i]['content'];
1602
                }
1603
1604
                $nextNonEmpty  = --$i;
1605
                $content       = $this->stripQuotes($content);
1606
                $stringContent = $content;
1607
            }
1608
1609
            /*
1610
             * Regexes based on the formats outlined in the manual, created by JRF.
1611
             * @link http://php.net/manual/en/language.types.float.php
1612
             */
1613
            $regexInt   = '`^\s*[0-9]+`';
1614
            $regexFloat = '`^\s*(?:[+-]?(?:(?:(?P<LNUM>[0-9]+)|(?P<DNUM>([0-9]*\.(?P>LNUM)|(?P>LNUM)\.[0-9]*)))[eE][+-]?(?P>LNUM))|(?P>DNUM))`';
1615
1616
            $intString   = preg_match($regexInt, $content, $intMatch);
1617
            $floatString = preg_match($regexFloat, $content, $floatMatch);
1618
1619
            // Does the text string start with a number ? If so, PHP would juggle it and use it as a number.
1620
            if ($allowFloats === false) {
1621
                if ($intString !== 1 || $floatString === 1) {
1622
                    if ($floatString === 1) {
1623
                        // Found float. Only integers targetted.
1624
                        return false;
1625
                    }
1626
1627
                    $content = 0.0;
1628
                } else {
1629
                    $content = (float) trim($intMatch[0]);
1630
                }
1631
            } else {
1632
                if ($intString !== 1 && $floatString !== 1) {
1633
                    $content = 0.0;
1634
                } else {
1635
                    $content = ($floatString === 1) ? (float) trim($floatMatch[0]) : (float) trim($intMatch[0]);
1636
                }
1637
            }
1638
1639
            // Allow for different behaviour for hex numeric strings between PHP 5 vs PHP 7.
1640
            if ($intString === 1 && trim($intMatch[0]) === '0'
1641
                && preg_match('`^\s*(0x[A-Fa-f0-9]+)`', $stringContent, $hexNumberString) === 1
1642
                && $this->supportsBelow('5.6') === true
1643
            ) {
1644
                // The filter extension still allows for hex numeric strings in PHP 7, so
1645
                // use that to get the numeric value if possible.
1646
                // If the filter extension is not available, the value will be zero, but so be it.
1647
                if (function_exists('filter_var')) {
1648
                    $filtered = filter_var($hexNumberString[1], FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX);
1649
                    if ($filtered !== false) {
1650
                        $content = $filtered;
1651
                    }
1652
                }
1653
            }
1654
        }
1655
1656
        // OK, so we have a number, now is there still more code after it ?
1657
        $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
1658
        if ($nextNonEmpty !== false) {
1659
            return false;
1660
        }
1661
1662
        if ($negativeNumber === true) {
1663
            $content = -$content;
1664
        }
1665
1666
        if ($allowFloats === false) {
1667
            return (int) $content;
1668
        }
1669
1670
        return $content;
1671
    }
1672
1673
1674
    /**
1675
     * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a list() construct.
1676
     *
1677
     * Note: A variety of PHPCS versions have bugs in the tokenizing of short arrays.
1678
     * In that case, the tokens are identified as T_OPEN/CLOSE_SQUARE_BRACKET.
1679
     *
1680
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1681
     * @param int                   $stackPtr  The position of the function call token.
1682
     *
1683
     * @return bool
1684
     */
1685
    public function isShortList(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1686
    {
1687
        $tokens = $phpcsFile->getTokens();
1688
1689
        // Check for the existence of the token.
1690
        if (isset($tokens[$stackPtr]) === false) {
1691
            return false;
1692
        }
1693
1694
        // Is this one of the tokens this function handles ?
1695
        if ($tokens[$stackPtr]['code'] !== T_OPEN_SHORT_ARRAY
1696
            && $tokens[$stackPtr]['code'] !== T_CLOSE_SHORT_ARRAY
1697
        ) {
1698
            return false;
1699
        }
1700
1701
        switch ($tokens[$stackPtr]['code']) {
1702
            case T_OPEN_SHORT_ARRAY:
1703
                if (isset($tokens[$stackPtr]['bracket_closer']) === true) {
1704
                    $opener = $stackPtr;
1705
                    $closer = $tokens[$stackPtr]['bracket_closer'];
1706
                }
1707
                break;
1708
1709
            case T_CLOSE_SHORT_ARRAY:
1710
                if (isset($tokens[$stackPtr]['bracket_opener']) === true) {
1711
                    $opener = $tokens[$stackPtr]['bracket_opener'];
1712
                    $closer = $stackPtr;
1713
                }
1714
                break;
1715
        }
1716
1717
        if (isset($opener, $closer) === false) {
1718
            // Parse error, live coding or real square bracket.
1719
            return false;
1720
        }
1721
1722
        /*
1723
         * PHPCS cross-version compatibility: work around for square brackets misidentified
1724
         * as short array when preceded by a variable variable in older PHPCS versions.
1725
         */
1726
        $prevNonEmpty = $phpcsFile->findPrevious(
1727
            \PHP_CodeSniffer_Tokens::$emptyTokens,
1728
            ($opener - 1),
0 ignored issues
show
Bug introduced by
The variable $opener does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1729
            null,
1730
            true,
1731
            null,
1732
            true
1733
        );
1734
1735
        if ($prevNonEmpty !== false
1736
            && $tokens[$prevNonEmpty]['code'] === T_CLOSE_CURLY_BRACKET
1737
            && isset($tokens[$prevNonEmpty]['bracket_opener']) === true
1738
        ) {
1739
            $maybeVariableVariable = $phpcsFile->findPrevious(
1740
                \PHP_CodeSniffer_Tokens::$emptyTokens,
1741
                ($tokens[$prevNonEmpty]['bracket_opener'] - 1),
1742
                null,
1743
                true,
1744
                null,
1745
                true
1746
            );
1747
1748
            if ($tokens[$maybeVariableVariable]['code'] === T_VARIABLE
1749
                || $tokens[$maybeVariableVariable]['code'] === T_DOLLAR
1750
            ) {
1751
                return false;
1752
            }
1753
        }
1754
1755
        $nextNonEmpty = $phpcsFile->findNext(
1756
            \PHP_CodeSniffer_Tokens::$emptyTokens,
1757
            ($closer + 1),
0 ignored issues
show
Bug introduced by
The variable $closer does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1758
            null,
1759
            true,
1760
            null,
1761
            true
1762
        );
1763
1764 View Code Duplication
        if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_EQUAL) {
1765
            return true;
1766
        }
1767
1768
        if ($prevNonEmpty !== false
1769
            && $tokens[$prevNonEmpty]['code'] === T_AS
1770
            && isset($tokens[$prevNonEmpty]['nested_parenthesis']) === true
1771
        ) {
1772
            $parentheses = $tokens[$prevNonEmpty]['nested_parenthesis'];
0 ignored issues
show
Unused Code introduced by
$parentheses is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1773
            $parentheses = array_reverse($tokens[$prevNonEmpty]['nested_parenthesis'], true);
1774
            foreach ($parentheses as $open => $close) {
1775
                if (isset($tokens[$open]['parenthesis_owner'])
1776
                    && $tokens[$tokens[$open]['parenthesis_owner']]['code'] === T_FOREACH
1777
                ) {
1778
                    return true;
1779
                }
1780
            }
1781
        }
1782
1783
        // Maybe this is a short list syntax nested inside another short list syntax ?
1784
        $parentOpener = $opener;
1785
        do {
1786
            $parentOpener = $phpcsFile->findPrevious(
1787
                array(T_OPEN_SHORT_ARRAY, T_OPEN_SQUARE_BRACKET),
1788
                ($parentOpener - 1),
1789
                null,
1790
                false,
1791
                null,
1792
                true
1793
            );
1794
1795
            if ($parentOpener === false) {
1796
                return false;
1797
            }
1798
1799
        } while (isset($tokens[$parentOpener]['bracket_closer']) === true
1800
            && $tokens[$parentOpener]['bracket_closer'] < $opener
1801
        );
1802
1803
        if (isset($tokens[$parentOpener]['bracket_closer']) === true
1804
            && $tokens[$parentOpener]['bracket_closer'] > $closer
1805
        ) {
1806
            // Work around tokenizer issue in PHPCS 2.0 - 2.7.
1807
            $phpcsVersion = PHPCSHelper::getVersion();
1808
            if ((version_compare($phpcsVersion, '2.0', '>') === true
1809
                && version_compare($phpcsVersion, '2.8', '<') === true)
1810
                && $tokens[$parentOpener]['code'] === T_OPEN_SQUARE_BRACKET
1811
            ) {
1812
                $nextNonEmpty = $phpcsFile->findNext(
1813
                    \PHP_CodeSniffer_Tokens::$emptyTokens,
1814
                    ($tokens[$parentOpener]['bracket_closer'] + 1),
1815
                    null,
1816
                    true,
1817
                    null,
1818
                    true
1819
                );
1820
1821 View Code Duplication
                if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_EQUAL) {
1822
                    return true;
1823
                }
1824
1825
                return false;
1826
            }
1827
1828
            return $this->isShortList($phpcsFile, $parentOpener);
1829
        }
1830
1831
        return false;
1832
    }
1833
}//end class
1834