Sniff::isShortList()   F
last analyzed

Complexity

Conditions 32
Paths 132

Size

Total Lines 133

Duplication

Lines 13
Ratio 9.77 %

Importance

Changes 0
Metric Value
dl 13
loc 133
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, an external standard for PHP_CodeSniffer.
4
 *
5
 * @package   PHPCompatibility
6
 * @copyright 2012-2019 PHPCompatibility Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCompatibility/PHPCompatibility
9
 */
10
11
namespace PHPCompatibility;
12
13
use PHPCompatibility\PHPCSHelper;
14
use PHP_CodeSniffer_Exception as PHPCS_Exception;
15
use PHP_CodeSniffer_File as File;
16
use PHP_CodeSniffer_Sniff as PHPCS_Sniff;
17
use PHP_CodeSniffer_Tokens as Tokens;
18
19
/**
20
 * Base class from which all PHPCompatibility sniffs extend.
21
 *
22
 * @since 5.6
23
 */
24
abstract class Sniff implements PHPCS_Sniff
25
{
26
27
    /**
28
     * Regex to match variables in a double quoted string.
29
     *
30
     * This matches plain variables, but also more complex variables, such
31
     * as $obj->prop, self::prop and $var[].
32
     *
33
     * @since 7.1.2
34
     *
35
     * @var string
36
     */
37
    const REGEX_COMPLEX_VARS = '`(?:(\{)?(?<!\\\\)\$)?(\{)?(?<!\\\\)\$(\{)?(?P<varname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:->\$?(?P>varname)|\[[^\]]+\]|::\$?(?P>varname)|\([^\)]*\))*(?(3)\}|)(?(2)\}|)(?(1)\}|)`';
38
39
    /**
40
     * List of superglobals as an array of strings.
41
     *
42
     * Used by the ForbiddenParameterShadowSuperGlobals and ForbiddenClosureUseVariableNames sniffs.
43
     *
44
     * @since 7.0.0
45
     * @since 7.1.4 Moved from the `ForbiddenParameterShadowSuperGlobals` sniff to the base `Sniff` class.
46
     *
47
     * @var array
48
     */
49
    protected $superglobals = array(
50
        '$GLOBALS'  => true,
51
        '$_SERVER'  => true,
52
        '$_GET'     => true,
53
        '$_POST'    => true,
54
        '$_FILES'   => true,
55
        '$_COOKIE'  => true,
56
        '$_SESSION' => true,
57
        '$_REQUEST' => true,
58
        '$_ENV'     => true,
59
    );
60
61
    /**
62
     * List of functions using hash algorithm as parameter (always the first parameter).
63
     *
64
     * Used by the new/removed hash algorithm sniffs.
65
     * Key is the function name, value is the 1-based parameter position in the function call.
66
     *
67
     * @since 5.5
68
     * @since 7.0.7 Moved from the `RemovedHashAlgorithms` sniff to the base `Sniff` class.
69
     *
70
     * @var array
71
     */
72
    protected $hashAlgoFunctions = array(
73
        'hash_file'      => 1,
74
        'hash_hmac_file' => 1,
75
        'hash_hmac'      => 1,
76
        'hash_init'      => 1,
77
        'hash_pbkdf2'    => 1,
78
        'hash'           => 1,
79
    );
80
81
82
    /**
83
     * List of functions which take an ini directive as parameter (always the first parameter).
84
     *
85
     * Used by the new/removed ini directives sniffs.
86
     * Key is the function name, value is the 1-based parameter position in the function call.
87
     *
88
     * @since 7.1.0
89
     *
90
     * @var array
91
     */
92
    protected $iniFunctions = array(
93
        'ini_get' => 1,
94
        'ini_set' => 1,
95
    );
96
97
98
    /**
99
     * Get the testVersion configuration variable.
100
     *
101
     * The testVersion configuration variable may be in any of the following formats:
102
     * 1) Omitted/empty, in which case no version is specified. This effectively
103
     *    disables all the checks for new PHP features provided by this standard.
104
     * 2) A single PHP version number, e.g. "5.4" in which case the standard checks that
105
     *    the code will run on that version of PHP (no deprecated features or newer
106
     *    features being used).
107
     * 3) A range, e.g. "5.0-5.5", in which case the standard checks the code will run
108
     *    on all PHP versions in that range, and that it doesn't use any features that
109
     *    were deprecated by the final version in the list, or which were not available
110
     *    for the first version in the list.
111
     *    We accept ranges where one of the components is missing, e.g. "-5.6" means
112
     *    all versions up to PHP 5.6, and "7.0-" means all versions above PHP 7.0.
113
     * PHP version numbers should always be in Major.Minor format.  Both "5", "5.3.2"
114
     * would be treated as invalid, and ignored.
115
     *
116
     * @since 7.0.0
117
     * @since 7.1.3 Now allows for partial ranges such as `5.2-`.
118
     *
119
     * @return array $arrTestVersions will hold an array containing min/max version
120
     *               of PHP that we are checking against (see above).  If only a
121
     *               single version number is specified, then this is used as
122
     *               both the min and max.
123
     *
124
     * @throws \PHP_CodeSniffer_Exception If testVersion is invalid.
125
     */
126
    private function getTestVersion()
127
    {
128
        static $arrTestVersions = array();
129
130
        $default     = array(null, null);
131
        $testVersion = trim(PHPCSHelper::getConfigData('testVersion'));
132
133
        if (empty($testVersion) === false && isset($arrTestVersions[$testVersion]) === false) {
134
135
            $arrTestVersions[$testVersion] = $default;
136
137
            if (preg_match('`^\d+\.\d+$`', $testVersion)) {
138
                $arrTestVersions[$testVersion] = array($testVersion, $testVersion);
139
                return $arrTestVersions[$testVersion];
140
            }
141
142
            if (preg_match('`^(\d+\.\d+)?\s*-\s*(\d+\.\d+)?$`', $testVersion, $matches)) {
143
                if (empty($matches[1]) === false || empty($matches[2]) === false) {
144
                    // If no lower-limit is set, we set the min version to 4.0.
145
                    // Whilst development focuses on PHP 5 and above, we also accept
146
                    // sniffs for PHP 4, so we include that as the minimum.
147
                    // (It makes no sense to support PHP 3 as this was effectively a
148
                    // different language).
149
                    $min = empty($matches[1]) ? '4.0' : $matches[1];
150
151
                    // If no upper-limit is set, we set the max version to 99.9.
152
                    $max = empty($matches[2]) ? '99.9' : $matches[2];
153
154
                    if (version_compare($min, $max, '>')) {
155
                        trigger_error(
156
                            "Invalid range in testVersion setting: '" . $testVersion . "'",
157
                            \E_USER_WARNING
158
                        );
159
                        return $default;
160
                    } else {
161
                        $arrTestVersions[$testVersion] = array($min, $max);
162
                        return $arrTestVersions[$testVersion];
163
                    }
164
                }
165
            }
166
167
            trigger_error(
168
                "Invalid testVersion setting: '" . $testVersion . "'",
169
                \E_USER_WARNING
170
            );
171
            return $default;
172
        }
173
174
        if (isset($arrTestVersions[$testVersion])) {
175
            return $arrTestVersions[$testVersion];
176
        }
177
178
        return $default;
179
    }
180
181
182
    /**
183
     * Check whether a specific PHP version is equal to or higher than the maximum
184
     * supported PHP version as provided by the user in `testVersion`.
185
     *
186
     * Should be used when sniffing for *old* PHP features (deprecated/removed).
187
     *
188
     * @since 5.6
189
     *
190
     * @param string $phpVersion A PHP version number in 'major.minor' format.
191
     *
192
     * @return bool True if testVersion has not been provided or if the PHP version
193
     *              is equal to or higher than the highest supported PHP version
194
     *              in testVersion. False otherwise.
195
     */
196 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...
197
    {
198
        $testVersion = $this->getTestVersion();
199
        $testVersion = $testVersion[1];
200
201
        if (\is_null($testVersion)
202
            || version_compare($testVersion, $phpVersion) >= 0
203
        ) {
204
            return true;
205
        } else {
206
            return false;
207
        }
208
    }
209
210
211
    /**
212
     * Check whether a specific PHP version is equal to or lower than the minimum
213
     * supported PHP version as provided by the user in `testVersion`.
214
     *
215
     * Should be used when sniffing for *new* PHP features.
216
     *
217
     * @since 5.6
218
     *
219
     * @param string $phpVersion A PHP version number in 'major.minor' format.
220
     *
221
     * @return bool True if the PHP version is equal to or lower than the lowest
222
     *              supported PHP version in testVersion.
223
     *              False otherwise or if no testVersion is provided.
224
     */
225 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...
226
    {
227
        $testVersion = $this->getTestVersion();
228
        $testVersion = $testVersion[0];
229
230
        if (\is_null($testVersion) === false
231
            && version_compare($testVersion, $phpVersion) <= 0
232
        ) {
233
            return true;
234
        } else {
235
            return false;
236
        }
237
    }
238
239
240
    /**
241
     * Add a PHPCS message to the output stack as either a warning or an error.
242
     *
243
     * @since 7.1.0
244
     *
245
     * @param \PHP_CodeSniffer_File $phpcsFile The file the message applies to.
246
     * @param string                $message   The message.
247
     * @param int                   $stackPtr  The position of the token
248
     *                                         the message relates to.
249
     * @param bool                  $isError   Whether to report the message as an
250
     *                                         'error' or 'warning'.
251
     *                                         Defaults to true (error).
252
     * @param string                $code      The error code for the message.
253
     *                                         Defaults to 'Found'.
254
     * @param array                 $data      Optional input for the data replacements.
255
     *
256
     * @return void
257
     */
258
    public function addMessage(File $phpcsFile, $message, $stackPtr, $isError, $code = 'Found', $data = array())
259
    {
260
        if ($isError === true) {
261
            $phpcsFile->addError($message, $stackPtr, $code, $data);
262
        } else {
263
            $phpcsFile->addWarning($message, $stackPtr, $code, $data);
264
        }
265
    }
266
267
268
    /**
269
     * Convert an arbitrary string to an alphanumeric string with underscores.
270
     *
271
     * Pre-empt issues with arbitrary strings being used as error codes in XML and PHP.
272
     *
273
     * @since 7.1.0
274
     *
275
     * @param string $baseString Arbitrary string.
276
     *
277
     * @return string
278
     */
279
    public function stringToErrorCode($baseString)
280
    {
281
        return preg_replace('`[^a-z0-9_]`i', '_', strtolower($baseString));
282
    }
283
284
285
    /**
286
     * Strip quotes surrounding an arbitrary string.
287
     *
288
     * Intended for use with the contents of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
289
     *
290
     * @since 7.0.6
291
     *
292
     * @param string $string The raw string.
293
     *
294
     * @return string String without quotes around it.
295
     */
296
    public function stripQuotes($string)
297
    {
298
        return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
299
    }
300
301
302
    /**
303
     * Strip variables from an arbitrary double quoted string.
304
     *
305
     * Intended for use with the contents of a T_DOUBLE_QUOTED_STRING.
306
     *
307
     * @since 7.1.2
308
     *
309
     * @param string $string The raw string.
310
     *
311
     * @return string String without variables in it.
312
     */
313
    public function stripVariables($string)
314
    {
315
        if (strpos($string, '$') === false) {
316
            return $string;
317
        }
318
319
        return preg_replace(self::REGEX_COMPLEX_VARS, '', $string);
320
    }
321
322
323
    /**
324
     * Make all top level array keys in an array lowercase.
325
     *
326
     * @since 7.1.0
327
     *
328
     * @param array $array Initial array.
329
     *
330
     * @return array Same array, but with all lowercase top level keys.
331
     */
332
    public function arrayKeysToLowercase($array)
333
    {
334
        return array_change_key_case($array, \CASE_LOWER);
335
    }
336
337
338
    /**
339
     * Checks if a function call has parameters.
340
     *
341
     * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
342
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
343
     *
344
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it
345
     * will detect whether the array has values or is empty.
346
     *
347
     * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/120
348
     * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/152
349
     *
350
     * @since 7.0.3
351
     *
352
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
353
     * @param int                   $stackPtr  The position of the function call token.
354
     *
355
     * @return bool
356
     */
357
    public function doesFunctionCallHaveParameters(File $phpcsFile, $stackPtr)
358
    {
359
        $tokens = $phpcsFile->getTokens();
360
361
        // Check for the existence of the token.
362
        if (isset($tokens[$stackPtr]) === false) {
363
            return false;
364
        }
365
366
        // Is this one of the tokens this function handles ?
367
        if (\in_array($tokens[$stackPtr]['code'], array(\T_STRING, \T_ARRAY, \T_OPEN_SHORT_ARRAY, \T_VARIABLE), true) === false) {
368
            return false;
369
        }
370
371
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
372
373
        // Deal with short array syntax.
374
        if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
375
            if (isset($tokens[$stackPtr]['bracket_closer']) === false) {
376
                return false;
377
            }
378
379
            if ($nextNonEmpty === $tokens[$stackPtr]['bracket_closer']) {
380
                // No parameters.
381
                return false;
382
            } else {
383
                return true;
384
            }
385
        }
386
387
        // Deal with function calls & long arrays.
388
        // Next non-empty token should be the open parenthesis.
389
        if ($nextNonEmpty === false && $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
390
            return false;
391
        }
392
393
        if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
394
            return false;
395
        }
396
397
        $closeParenthesis = $tokens[$nextNonEmpty]['parenthesis_closer'];
398
        $nextNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $nextNonEmpty + 1, $closeParenthesis + 1, true);
399
400
        if ($nextNextNonEmpty === $closeParenthesis) {
401
            // No parameters.
402
            return false;
403
        }
404
405
        return true;
406
    }
407
408
409
    /**
410
     * Count the number of parameters a function call has been passed.
411
     *
412
     * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
413
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
414
     *
415
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
416
     * it will return the number of values in the array.
417
     *
418
     * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/111
419
     * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/114
420
     * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/151
421
     *
422
     * @since 7.0.3
423
     *
424
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
425
     * @param int                   $stackPtr  The position of the function call token.
426
     *
427
     * @return int
428
     */
429
    public function getFunctionCallParameterCount(File $phpcsFile, $stackPtr)
430
    {
431
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
432
            return 0;
433
        }
434
435
        return \count($this->getFunctionCallParameters($phpcsFile, $stackPtr));
436
    }
437
438
439
    /**
440
     * Get information on all parameters passed to a function call.
441
     *
442
     * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
443
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
444
     *
445
     * Will return an multi-dimentional array with the start token pointer, end token
446
     * pointer and raw parameter value for all parameters. Index will be 1-based.
447
     * If no parameters are found, will return an empty array.
448
     *
449
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
450
     * it will tokenize the values / key/value pairs contained in the array call.
451
     *
452
     * @since 7.0.5 Split off from the `getFunctionCallParameterCount()` method.
453
     *
454
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
455
     * @param int                   $stackPtr  The position of the function call token.
456
     *
457
     * @return array
458
     */
459
    public function getFunctionCallParameters(File $phpcsFile, $stackPtr)
460
    {
461
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
462
            return array();
463
        }
464
465
        // Ok, we know we have a T_STRING, T_VARIABLE, T_ARRAY or T_OPEN_SHORT_ARRAY with parameters
466
        // and valid open & close brackets/parenthesis.
467
        $tokens = $phpcsFile->getTokens();
468
469
        // Mark the beginning and end tokens.
470
        if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
471
            $opener = $stackPtr;
472
            $closer = $tokens[$stackPtr]['bracket_closer'];
473
474
            $nestedParenthesisCount = 0;
475
476
        } else {
477
            $opener = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
478
            $closer = $tokens[$opener]['parenthesis_closer'];
479
480
            $nestedParenthesisCount = 1;
481
        }
482
483
        // Which nesting level is the one we are interested in ?
484 View Code Duplication
        if (isset($tokens[$opener]['nested_parenthesis'])) {
0 ignored issues
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...
485
            $nestedParenthesisCount += \count($tokens[$opener]['nested_parenthesis']);
486
        }
487
488
        $parameters = array();
489
        $nextComma  = $opener;
490
        $paramStart = $opener + 1;
491
        $cnt        = 1;
492
        while (($nextComma = $phpcsFile->findNext(array(\T_COMMA, $tokens[$closer]['code'], \T_OPEN_SHORT_ARRAY, \T_CLOSURE), $nextComma + 1, $closer + 1)) !== false) {
493
            // Ignore anything within short array definition brackets.
494
            if ($tokens[$nextComma]['type'] === 'T_OPEN_SHORT_ARRAY'
495
                && (isset($tokens[$nextComma]['bracket_opener'])
496
                    && $tokens[$nextComma]['bracket_opener'] === $nextComma)
497
                && isset($tokens[$nextComma]['bracket_closer'])
498
            ) {
499
                // Skip forward to the end of the short array definition.
500
                $nextComma = $tokens[$nextComma]['bracket_closer'];
501
                continue;
502
            }
503
504
            // Skip past closures passed as function parameters.
505 View Code Duplication
            if ($tokens[$nextComma]['type'] === 'T_CLOSURE'
0 ignored issues
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...
506
                && (isset($tokens[$nextComma]['scope_condition'])
507
                    && $tokens[$nextComma]['scope_condition'] === $nextComma)
508
                && isset($tokens[$nextComma]['scope_closer'])
509
            ) {
510
                // Skip forward to the end of the closure declaration.
511
                $nextComma = $tokens[$nextComma]['scope_closer'];
512
                continue;
513
            }
514
515
            // Ignore comma's at a lower nesting level.
516
            if ($tokens[$nextComma]['type'] === 'T_COMMA'
517
                && isset($tokens[$nextComma]['nested_parenthesis'])
518
                && \count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount
519
            ) {
520
                continue;
521
            }
522
523
            // Ignore closing parenthesis/bracket if not 'ours'.
524
            if ($tokens[$nextComma]['type'] === $tokens[$closer]['type'] && $nextComma !== $closer) {
525
                continue;
526
            }
527
528
            // Ok, we've reached the end of the parameter.
529
            $parameters[$cnt]['start'] = $paramStart;
530
            $parameters[$cnt]['end']   = $nextComma - 1;
531
            $parameters[$cnt]['raw']   = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart)));
532
533
            /*
534
             * Check if there are more tokens before the closing parenthesis.
535
             * Prevents code like the following from setting a third parameter:
536
             * `functionCall( $param1, $param2, );`.
537
             */
538
            $hasNextParam = $phpcsFile->findNext(Tokens::$emptyTokens, $nextComma + 1, $closer, true, null, true);
539
            if ($hasNextParam === false) {
540
                break;
541
            }
542
543
            // Prepare for the next parameter.
544
            $paramStart = $nextComma + 1;
545
            $cnt++;
546
        }
547
548
        return $parameters;
549
    }
550
551
552
    /**
553
     * Get information on a specific parameter passed to a function call.
554
     *
555
     * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
556
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
557
     *
558
     * Will return a array with the start token pointer, end token pointer and the raw value
559
     * of the parameter at a specific offset.
560
     * If the specified parameter is not found, will return false.
561
     *
562
     * @since 7.0.5
563
     *
564
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
565
     * @param int                   $stackPtr    The position of the function call token.
566
     * @param int                   $paramOffset The 1-based index position of the parameter to retrieve.
567
     *
568
     * @return array|false
569
     */
570
    public function getFunctionCallParameter(File $phpcsFile, $stackPtr, $paramOffset)
571
    {
572
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
573
574
        if (isset($parameters[$paramOffset]) === false) {
575
            return false;
576
        } else {
577
            return $parameters[$paramOffset];
578
        }
579
    }
580
581
582
    /**
583
     * Verify whether a token is within a scoped condition.
584
     *
585
     * If the optional $validScopes parameter has been passed, the function
586
     * will check that the token has at least one condition which is of a
587
     * type defined in $validScopes.
588
     *
589
     * @since 7.0.5 Largely split off from the `inClassScope()` method.
590
     *
591
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
592
     * @param int                   $stackPtr    The position of the token.
593
     * @param array|int             $validScopes Optional. Array of valid scopes
594
     *                                           or int value of a valid scope.
595
     *                                           Pass the T_.. constant(s) for the
596
     *                                           desired scope to this parameter.
597
     *
598
     * @return bool Without the optional $scopeTypes: True if within a scope, false otherwise.
599
     *              If the $scopeTypes are set: True if *one* of the conditions is a
600
     *              valid scope, false otherwise.
601
     */
602
    public function tokenHasScope(File $phpcsFile, $stackPtr, $validScopes = null)
603
    {
604
        $tokens = $phpcsFile->getTokens();
605
606
        // Check for the existence of the token.
607
        if (isset($tokens[$stackPtr]) === false) {
608
            return false;
609
        }
610
611
        // No conditions = no scope.
612
        if (empty($tokens[$stackPtr]['conditions'])) {
613
            return false;
614
        }
615
616
        // Ok, there are conditions, do we have to check for specific ones ?
617
        if (isset($validScopes) === false) {
618
            return true;
619
        }
620
621
        return $phpcsFile->hasCondition($stackPtr, $validScopes);
622
    }
623
624
625
    /**
626
     * Verify whether a token is within a class scope.
627
     *
628
     * @since 7.0.3
629
     *
630
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
631
     * @param int                   $stackPtr  The position of the token.
632
     * @param bool                  $strict    Whether to strictly check for the T_CLASS
633
     *                                         scope or also accept interfaces and traits
634
     *                                         as scope.
635
     *
636
     * @return bool True if within class scope, false otherwise.
637
     */
638
    public function inClassScope(File $phpcsFile, $stackPtr, $strict = true)
639
    {
640
        $validScopes = array(\T_CLASS);
641
        if (\defined('T_ANON_CLASS') === true) {
642
            $validScopes[] = \T_ANON_CLASS;
643
        }
644
645
        if ($strict === false) {
646
            $validScopes[] = \T_INTERFACE;
647
            $validScopes[] = \T_TRAIT;
648
        }
649
650
        return $phpcsFile->hasCondition($stackPtr, $validScopes);
651
    }
652
653
654
    /**
655
     * Returns the fully qualified class name for a new class instantiation.
656
     *
657
     * Returns an empty string if the class name could not be reliably inferred.
658
     *
659
     * @since 7.0.3
660
     *
661
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
662
     * @param int                   $stackPtr  The position of a T_NEW token.
663
     *
664
     * @return string
665
     */
666
    public function getFQClassNameFromNewToken(File $phpcsFile, $stackPtr)
667
    {
668
        $tokens = $phpcsFile->getTokens();
669
670
        // Check for the existence of the token.
671
        if (isset($tokens[$stackPtr]) === false) {
672
            return '';
673
        }
674
675
        if ($tokens[$stackPtr]['code'] !== \T_NEW) {
676
            return '';
677
        }
678
679
        $start = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
680
        if ($start === false) {
681
            return '';
682
        }
683
684
        // Bow out if the next token is a variable as we don't know where it was defined.
685
        if ($tokens[$start]['code'] === \T_VARIABLE) {
686
            return '';
687
        }
688
689
        // Bow out if the next token is the class keyword.
690
        if ($tokens[$start]['type'] === 'T_ANON_CLASS' || $tokens[$start]['code'] === \T_CLASS) {
691
            return '';
692
        }
693
694
        $find = array(
695
            \T_NS_SEPARATOR,
696
            \T_STRING,
697
            \T_NAMESPACE,
698
            \T_WHITESPACE,
699
        );
700
701
        $end       = $phpcsFile->findNext($find, ($start + 1), null, true, null, true);
702
        $className = $phpcsFile->getTokensAsString($start, ($end - $start));
703
        $className = trim($className);
704
705
        return $this->getFQName($phpcsFile, $stackPtr, $className);
706
    }
707
708
709
    /**
710
     * Returns the fully qualified name of the class that the specified class extends.
711
     *
712
     * Returns an empty string if the class does not extend another class or if
713
     * the class name could not be reliably inferred.
714
     *
715
     * @since 7.0.3
716
     *
717
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
718
     * @param int                   $stackPtr  The position of a T_CLASS token.
719
     *
720
     * @return string
721
     */
722
    public function getFQExtendedClassName(File $phpcsFile, $stackPtr)
723
    {
724
        $tokens = $phpcsFile->getTokens();
725
726
        // Check for the existence of the token.
727
        if (isset($tokens[$stackPtr]) === false) {
728
            return '';
729
        }
730
731 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== \T_CLASS
0 ignored issues
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...
732
            && $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
733
            && $tokens[$stackPtr]['type'] !== 'T_INTERFACE'
734
        ) {
735
            return '';
736
        }
737
738
        $extends = PHPCSHelper::findExtendedClassName($phpcsFile, $stackPtr);
739
        if (empty($extends) || \is_string($extends) === false) {
740
            return '';
741
        }
742
743
        return $this->getFQName($phpcsFile, $stackPtr, $extends);
744
    }
745
746
747
    /**
748
     * Returns the class name for the static usage of a class.
749
     * This can be a call to a method, the use of a property or constant.
750
     *
751
     * Returns an empty string if the class name could not be reliably inferred.
752
     *
753
     * @since 7.0.3
754
     *
755
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
756
     * @param int                   $stackPtr  The position of a T_NEW token.
757
     *
758
     * @return string
759
     */
760
    public function getFQClassNameFromDoubleColonToken(File $phpcsFile, $stackPtr)
761
    {
762
        $tokens = $phpcsFile->getTokens();
763
764
        // Check for the existence of the token.
765
        if (isset($tokens[$stackPtr]) === false) {
766
            return '';
767
        }
768
769
        if ($tokens[$stackPtr]['code'] !== \T_DOUBLE_COLON) {
770
            return '';
771
        }
772
773
        // Nothing to do if previous token is a variable as we don't know where it was defined.
774
        if ($tokens[$stackPtr - 1]['code'] === \T_VARIABLE) {
775
            return '';
776
        }
777
778
        // Nothing to do if 'parent' or 'static' as we don't know how far the class tree extends.
779
        if (\in_array($tokens[$stackPtr - 1]['code'], array(\T_PARENT, \T_STATIC), true)) {
780
            return '';
781
        }
782
783
        // Get the classname from the class declaration if self is used.
784
        if ($tokens[$stackPtr - 1]['code'] === \T_SELF) {
785
            $classDeclarationPtr = $phpcsFile->findPrevious(\T_CLASS, $stackPtr - 1);
786
            if ($classDeclarationPtr === false) {
787
                return '';
788
            }
789
            $className = $phpcsFile->getDeclarationName($classDeclarationPtr);
790
            return $this->getFQName($phpcsFile, $classDeclarationPtr, $className);
791
        }
792
793
        $find = array(
794
            \T_NS_SEPARATOR,
795
            \T_STRING,
796
            \T_NAMESPACE,
797
            \T_WHITESPACE,
798
        );
799
800
        $start = $phpcsFile->findPrevious($find, $stackPtr - 1, null, true, null, true);
801
        if ($start === false || isset($tokens[($start + 1)]) === false) {
802
            return '';
803
        }
804
805
        $start     = ($start + 1);
806
        $className = $phpcsFile->getTokensAsString($start, ($stackPtr - $start));
807
        $className = trim($className);
808
809
        return $this->getFQName($phpcsFile, $stackPtr, $className);
810
    }
811
812
813
    /**
814
     * Get the Fully Qualified name for a class/function/constant etc.
815
     *
816
     * Checks if a class/function/constant name is already fully qualified and
817
     * if not, enrich it with the relevant namespace information.
818
     *
819
     * @since 7.0.3
820
     *
821
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
822
     * @param int                   $stackPtr  The position of the token.
823
     * @param string                $name      The class / function / constant name.
824
     *
825
     * @return string
826
     */
827
    public function getFQName(File $phpcsFile, $stackPtr, $name)
828
    {
829
        if (strpos($name, '\\') === 0) {
830
            // Already fully qualified.
831
            return $name;
832
        }
833
834
        // Remove the namespace keyword if used.
835
        if (strpos($name, 'namespace\\') === 0) {
836
            $name = substr($name, 10);
837
        }
838
839
        $namespace = $this->determineNamespace($phpcsFile, $stackPtr);
840
841
        if ($namespace === '') {
842
            return '\\' . $name;
843
        } else {
844
            return '\\' . $namespace . '\\' . $name;
845
        }
846
    }
847
848
849
    /**
850
     * Is the class/function/constant name namespaced or global ?
851
     *
852
     * @since 7.0.3
853
     *
854
     * @param string $FQName Fully Qualified name of a class, function etc.
855
     *                       I.e. should always start with a `\`.
856
     *
857
     * @return bool True if namespaced, false if global.
858
     *
859
     * @throws \PHP_CodeSniffer_Exception If the name in the passed parameter
860
     *                                    is not fully qualified.
861
     */
862
    public function isNamespaced($FQName)
863
    {
864
        if (strpos($FQName, '\\') !== 0) {
865
            throw new PHPCS_Exception('$FQName must be a fully qualified name');
866
        }
867
868
        return (strpos(substr($FQName, 1), '\\') !== false);
869
    }
870
871
872
    /**
873
     * Determine the namespace name an arbitrary token lives in.
874
     *
875
     * @since 7.0.3
876
     *
877
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
878
     * @param int                   $stackPtr  The token position for which to determine the namespace.
879
     *
880
     * @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
881
     */
882
    public function determineNamespace(File $phpcsFile, $stackPtr)
883
    {
884
        $tokens = $phpcsFile->getTokens();
885
886
        // Check for the existence of the token.
887
        if (isset($tokens[$stackPtr]) === false) {
888
            return '';
889
        }
890
891
        // Check for scoped namespace {}.
892
        if (empty($tokens[$stackPtr]['conditions']) === false) {
893
            $namespacePtr = $phpcsFile->getCondition($stackPtr, \T_NAMESPACE);
894
            if ($namespacePtr !== false) {
895
                $namespace = $this->getDeclaredNamespaceName($phpcsFile, $namespacePtr);
896
                if ($namespace !== false) {
897
                    return $namespace;
898
                }
899
900
                // We are in a scoped namespace, but couldn't determine the name. Searching for a global namespace is futile.
901
                return '';
902
            }
903
        }
904
905
        /*
906
         * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
907
         * Keeping in mind that:
908
         * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
909
         * - the namespace keyword can also be used as part of a function/method call and such.
910
         * - that a non-named namespace resolves to the global namespace.
911
         */
912
        $previousNSToken = $stackPtr;
913
        $namespace       = false;
914
        do {
915
            $previousNSToken = $phpcsFile->findPrevious(\T_NAMESPACE, ($previousNSToken - 1));
916
917
            // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
918
            if (empty($tokens[$previousNSToken]['scope_condition']) === false && $tokens[$previousNSToken]['scope_condition'] === $previousNSToken) {
919
                break;
920
            }
921
922
            $namespace = $this->getDeclaredNamespaceName($phpcsFile, $previousNSToken);
923
924
        } while ($namespace === false && $previousNSToken !== false);
925
926
        // If we still haven't got a namespace, return an empty string.
927
        if ($namespace === false) {
928
            return '';
929
        } else {
930
            return $namespace;
931
        }
932
    }
933
934
    /**
935
     * Get the complete namespace name for a namespace declaration.
936
     *
937
     * For hierarchical namespaces, the name will be composed of several tokens,
938
     * i.e. MyProject\Sub\Level which will be returned together as one string.
939
     *
940
     * @since 7.0.3
941
     *
942
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
943
     * @param int|bool              $stackPtr  The position of a T_NAMESPACE token.
944
     *
945
     * @return string|false Namespace name or false if not a namespace declaration.
946
     *                      Namespace name can be an empty string for global namespace declaration.
947
     */
948
    public function getDeclaredNamespaceName(File $phpcsFile, $stackPtr)
949
    {
950
        $tokens = $phpcsFile->getTokens();
951
952
        // Check for the existence of the token.
953 View Code Duplication
        if ($stackPtr === false || isset($tokens[$stackPtr]) === false) {
0 ignored issues
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...
954
            return false;
955
        }
956
957
        if ($tokens[$stackPtr]['code'] !== \T_NAMESPACE) {
958
            return false;
959
        }
960
961
        if ($tokens[($stackPtr + 1)]['code'] === \T_NS_SEPARATOR) {
962
            // Not a namespace declaration, but use of, i.e. `namespace\someFunction();`.
963
            return false;
964
        }
965
966
        $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
967
        if ($tokens[$nextToken]['code'] === \T_OPEN_CURLY_BRACKET) {
968
            /*
969
             * Declaration for global namespace when using multiple namespaces in a file.
970
             * I.e.: `namespace {}`.
971
             */
972
            return '';
973
        }
974
975
        // Ok, this should be a namespace declaration, so get all the parts together.
976
        $validTokens = array(
977
            \T_STRING       => true,
978
            \T_NS_SEPARATOR => true,
979
            \T_WHITESPACE   => true,
980
        );
981
982
        $namespaceName = '';
983
        while (isset($validTokens[$tokens[$nextToken]['code']]) === true) {
984
            $namespaceName .= trim($tokens[$nextToken]['content']);
985
            $nextToken++;
986
        }
987
988
        return $namespaceName;
989
    }
990
991
992
    /**
993
     * Get the stack pointer for a return type token for a given function.
994
     *
995
     * Compatible layer for older PHPCS versions which don't recognize
996
     * return type hints correctly.
997
     *
998
     * Expects to be passed T_RETURN_TYPE, T_FUNCTION or T_CLOSURE token.
999
     *
1000
     * @since 7.1.2
1001
     *
1002
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1003
     * @param int                   $stackPtr  The position of the token.
1004
     *
1005
     * @return int|false Stack pointer to the return type token or false if
1006
     *                   no return type was found or the passed token was
1007
     *                   not of the correct type.
1008
     */
1009
    public function getReturnTypeHintToken(File $phpcsFile, $stackPtr)
1010
    {
1011
        $tokens = $phpcsFile->getTokens();
1012
1013
        if (\defined('T_RETURN_TYPE') && $tokens[$stackPtr]['code'] === \T_RETURN_TYPE) {
1014
            return $stackPtr;
1015
        }
1016
1017 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== \T_FUNCTION && $tokens[$stackPtr]['code'] !== \T_CLOSURE) {
0 ignored issues
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...
1018
            return false;
1019
        }
1020
1021
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === false) {
1022
            return false;
1023
        }
1024
1025
        // Allow for interface and abstract method declarations.
1026
        $endOfFunctionDeclaration = null;
1027
        if (isset($tokens[$stackPtr]['scope_opener'])) {
1028
            $endOfFunctionDeclaration = $tokens[$stackPtr]['scope_opener'];
1029
        } else {
1030
            $nextSemiColon = $phpcsFile->findNext(\T_SEMICOLON, ($tokens[$stackPtr]['parenthesis_closer'] + 1), null, false, null, true);
1031
            if ($nextSemiColon !== false) {
1032
                $endOfFunctionDeclaration = $nextSemiColon;
1033
            }
1034
        }
1035
1036
        if (isset($endOfFunctionDeclaration) === false) {
1037
            return false;
1038
        }
1039
1040
        $hasColon = $phpcsFile->findNext(
1041
            array(\T_COLON, \T_INLINE_ELSE),
1042
            ($tokens[$stackPtr]['parenthesis_closer'] + 1),
1043
            $endOfFunctionDeclaration
1044
        );
1045
        if ($hasColon === false) {
1046
            return false;
1047
        }
1048
1049
        /*
1050
         * - `self`, `parent` and `callable` are not being recognized as return types in PHPCS < 2.6.0.
1051
         * - Return types are not recognized at all in PHPCS < 2.4.0.
1052
         * - The T_RETURN_TYPE token is defined, but no longer in use since PHPCS 3.3.0+.
1053
         *   The token will now be tokenized as T_STRING.
1054
         * - An `array` (return) type declaration was tokenized as `T_ARRAY_HINT` in PHPCS 2.3.3 - 3.2.3
1055
         *   to prevent confusing sniffs looking for array declarations.
1056
         *   As of PHPCS 3.3.0 `array` as a type declaration will be tokenized as `T_STRING`.
1057
         */
1058
        $unrecognizedTypes = array(
1059
            \T_CALLABLE,
1060
            \T_SELF,
1061
            \T_PARENT,
1062
            \T_ARRAY, // PHPCS < 2.4.0.
1063
            \T_STRING,
1064
        );
1065
1066
        return $phpcsFile->findPrevious($unrecognizedTypes, ($endOfFunctionDeclaration - 1), $hasColon);
1067
    }
1068
1069
1070
    /**
1071
     * Get the complete return type declaration for a given function.
1072
     *
1073
     * Cross-version compatible way to retrieve the complete return type declaration.
1074
     *
1075
     * For a classname-based return type, PHPCS, as well as the Sniff::getReturnTypeHintToken()
1076
     * method will mark the classname as the return type token.
1077
     * This method will find preceeding namespaces and namespace separators and will return a
1078
     * string containing the qualified return type declaration.
1079
     *
1080
     * Expects to be passed a T_RETURN_TYPE token or the return value from a call to
1081
     * the Sniff::getReturnTypeHintToken() method.
1082
     *
1083
     * @since 8.2.0
1084
     *
1085
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1086
     * @param int                   $stackPtr  The position of the return type token.
1087
     *
1088
     * @return string The name of the return type token.
1089
     */
1090
    public function getReturnTypeHintName(File $phpcsFile, $stackPtr)
1091
    {
1092
        $tokens = $phpcsFile->getTokens();
1093
1094
        // In older PHPCS versions, the nullable indicator will turn a return type colon into a T_INLINE_ELSE.
1095
        $colon = $phpcsFile->findPrevious(array(\T_COLON, \T_INLINE_ELSE, \T_FUNCTION, \T_CLOSE_PARENTHESIS), ($stackPtr - 1));
1096
        if ($colon === false
1097
            || ($tokens[$colon]['code'] !== \T_COLON && $tokens[$colon]['code'] !== \T_INLINE_ELSE)
1098
        ) {
1099
            // Shouldn't happen, just in case.
1100
            return '';
1101
        }
1102
1103
        $returnTypeHint = '';
1104
        for ($i = ($colon + 1); $i <= $stackPtr; $i++) {
1105
            // As of PHPCS 3.3.0+, all tokens are tokenized as "normal", so T_CALLABLE, T_SELF etc are
1106
            // all possible, just exclude anything that's regarded as empty and the nullable indicator.
1107
            if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
1108
                continue;
1109
            }
1110
1111
            if ($tokens[$i]['type'] === 'T_NULLABLE') {
1112
                continue;
1113
            }
1114
1115
            if (\defined('T_NULLABLE') === false && $tokens[$i]['code'] === \T_INLINE_THEN) {
1116
                // Old PHPCS.
1117
                continue;
1118
            }
1119
1120
            $returnTypeHint .= $tokens[$i]['content'];
1121
        }
1122
1123
        return $returnTypeHint;
1124
    }
1125
1126
1127
    /**
1128
     * Check whether a T_VARIABLE token is a class property declaration.
1129
     *
1130
     * Compatibility layer for PHPCS cross-version compatibility
1131
     * as PHPCS 2.4.0 - 2.7.1 does not have good enough support for
1132
     * anonymous classes. Along the same lines, the`getMemberProperties()`
1133
     * method does not support the `var` prefix.
1134
     *
1135
     * @since 7.1.4
1136
     *
1137
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1138
     * @param int                   $stackPtr  The position in the stack of the
1139
     *                                         T_VARIABLE token to verify.
1140
     *
1141
     * @return bool
1142
     */
1143
    public function isClassProperty(File $phpcsFile, $stackPtr)
1144
    {
1145
        $tokens = $phpcsFile->getTokens();
1146
1147 View Code Duplication
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_VARIABLE) {
0 ignored issues
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...
1148
            return false;
1149
        }
1150
1151
        // Note: interfaces can not declare properties.
1152
        $validScopes = array(
1153
            'T_CLASS'      => true,
1154
            'T_ANON_CLASS' => true,
1155
            'T_TRAIT'      => true,
1156
        );
1157
1158
        $scopePtr = $this->validDirectScope($phpcsFile, $stackPtr, $validScopes);
1159
        if ($scopePtr !== false) {
1160
            // Make sure it's not a method parameter.
1161
            if (empty($tokens[$stackPtr]['nested_parenthesis']) === true) {
1162
                return true;
1163
            } else {
1164
                $parenthesis = array_keys($tokens[$stackPtr]['nested_parenthesis']);
1165
                $deepestOpen = array_pop($parenthesis);
1166
                if ($deepestOpen < $scopePtr
1167
                    || isset($tokens[$deepestOpen]['parenthesis_owner']) === false
1168
                    || $tokens[$tokens[$deepestOpen]['parenthesis_owner']]['code'] !== \T_FUNCTION
1169
                ) {
1170
                    return true;
1171
                }
1172
            }
1173
        }
1174
1175
        return false;
1176
    }
1177
1178
1179
    /**
1180
     * Check whether a T_CONST token is a class constant declaration.
1181
     *
1182
     * @since 7.1.4
1183
     *
1184
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1185
     * @param int                   $stackPtr  The position in the stack of the
1186
     *                                         T_CONST token to verify.
1187
     *
1188
     * @return bool
1189
     */
1190
    public function isClassConstant(File $phpcsFile, $stackPtr)
1191
    {
1192
        $tokens = $phpcsFile->getTokens();
1193
1194 View Code Duplication
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_CONST) {
0 ignored issues
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...
1195
            return false;
1196
        }
1197
1198
        // Note: traits can not declare constants.
1199
        $validScopes = array(
1200
            'T_CLASS'      => true,
1201
            'T_ANON_CLASS' => true,
1202
            'T_INTERFACE'  => true,
1203
        );
1204
        if ($this->validDirectScope($phpcsFile, $stackPtr, $validScopes) !== false) {
1205
            return true;
1206
        }
1207
1208
        return false;
1209
    }
1210
1211
1212
    /**
1213
     * Check whether the direct wrapping scope of a token is within a limited set of
1214
     * acceptable tokens.
1215
     *
1216
     * Used to check, for instance, if a T_CONST is a class constant.
1217
     *
1218
     * @since 7.1.4
1219
     *
1220
     * @param \PHP_CodeSniffer_File $phpcsFile   Instance of phpcsFile.
1221
     * @param int                   $stackPtr    The position in the stack of the
1222
     *                                           token to verify.
1223
     * @param array                 $validScopes Array of token types.
1224
     *                                           Keys should be the token types in string
1225
     *                                           format to allow for newer token types.
1226
     *                                           Value is irrelevant.
1227
     *
1228
     * @return int|bool StackPtr to the scope if valid, false otherwise.
1229
     */
1230
    protected function validDirectScope(File $phpcsFile, $stackPtr, $validScopes)
1231
    {
1232
        $tokens = $phpcsFile->getTokens();
1233
1234
        if (empty($tokens[$stackPtr]['conditions']) === true) {
1235
            return false;
1236
        }
1237
1238
        /*
1239
         * Check only the direct wrapping scope of the token.
1240
         */
1241
        $conditions = array_keys($tokens[$stackPtr]['conditions']);
1242
        $ptr        = array_pop($conditions);
1243
1244
        if (isset($tokens[$ptr]) === false) {
1245
            return false;
1246
        }
1247
1248
        if (isset($validScopes[$tokens[$ptr]['type']]) === true) {
1249
            return $ptr;
1250
        }
1251
1252
        return false;
1253
    }
1254
1255
1256
    /**
1257
     * Get an array of just the type hints from a function declaration.
1258
     *
1259
     * Expects to be passed T_FUNCTION or T_CLOSURE token.
1260
     *
1261
     * Strips potential nullable indicator and potential global namespace
1262
     * indicator from the type hints before returning them.
1263
     *
1264
     * @since 7.1.4
1265
     *
1266
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1267
     * @param int                   $stackPtr  The position of the token.
1268
     *
1269
     * @return array Array with type hints or an empty array if
1270
     *               - the function does not have any parameters
1271
     *               - no type hints were found
1272
     *               - or the passed token was not of the correct type.
1273
     */
1274
    public function getTypeHintsFromFunctionDeclaration(File $phpcsFile, $stackPtr)
1275
    {
1276
        $tokens = $phpcsFile->getTokens();
1277
1278 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== \T_FUNCTION && $tokens[$stackPtr]['code'] !== \T_CLOSURE) {
0 ignored issues
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...
1279
            return array();
1280
        }
1281
1282
        $parameters = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
1283
        if (empty($parameters) || \is_array($parameters) === false) {
1284
            return array();
1285
        }
1286
1287
        $typeHints = array();
1288
1289
        foreach ($parameters as $param) {
1290
            if ($param['type_hint'] === '') {
1291
                continue;
1292
            }
1293
1294
            // Strip off potential nullable indication.
1295
            $typeHint = ltrim($param['type_hint'], '?');
1296
1297
            // Strip off potential (global) namespace indication.
1298
            $typeHint = ltrim($typeHint, '\\');
1299
1300
            if ($typeHint !== '') {
1301
                $typeHints[] = $typeHint;
1302
            }
1303
        }
1304
1305
        return $typeHints;
1306
    }
1307
1308
1309
    /**
1310
     * Get the hash algorithm name from the parameter in a hash function call.
1311
     *
1312
     * @since 7.0.7 Logic was originally contained in the `RemovedHashAlgorithms` sniff.
1313
     *
1314
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1315
     * @param int                   $stackPtr  The position of the T_STRING function token.
1316
     *
1317
     * @return string|false The algorithm name without quotes if this was a relevant hash
1318
     *                      function call or false if it was not.
1319
     */
1320
    public function getHashAlgorithmParameter(File $phpcsFile, $stackPtr)
1321
    {
1322
        $tokens = $phpcsFile->getTokens();
1323
1324
        // Check for the existence of the token.
1325
        if (isset($tokens[$stackPtr]) === false) {
1326
            return false;
1327
        }
1328
1329
        if ($tokens[$stackPtr]['code'] !== \T_STRING) {
1330
            return false;
1331
        }
1332
1333
        $functionName   = $tokens[$stackPtr]['content'];
1334
        $functionNameLc = strtolower($functionName);
1335
1336
        // Bow out if not one of the functions we're targetting.
1337
        if (isset($this->hashAlgoFunctions[$functionNameLc]) === false) {
1338
            return false;
1339
        }
1340
1341
        // Get the parameter from the function call which should contain the algorithm name.
1342
        $algoParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->hashAlgoFunctions[$functionNameLc]);
1343
        if ($algoParam === false) {
1344
            return false;
1345
        }
1346
1347
        // Algorithm is a text string, so we need to remove the quotes.
1348
        $algo = strtolower(trim($algoParam['raw']));
1349
        $algo = $this->stripQuotes($algo);
1350
1351
        return $algo;
1352
    }
1353
1354
1355
    /**
1356
     * Determine whether an arbitrary T_STRING token is the use of a global constant.
1357
     *
1358
     * @since 8.1.0
1359
     *
1360
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1361
     * @param int                   $stackPtr  The position of the T_STRING token.
1362
     *
1363
     * @return bool
1364
     */
1365
    public function isUseOfGlobalConstant(File $phpcsFile, $stackPtr)
1366
    {
1367
        static $isLowPHPCS, $isLowPHP;
1368
1369
        $tokens = $phpcsFile->getTokens();
1370
1371
        // Check for the existence of the token.
1372
        if (isset($tokens[$stackPtr]) === false) {
1373
            return false;
1374
        }
1375
1376
        // Is this one of the tokens this function handles ?
1377
        if ($tokens[$stackPtr]['code'] !== \T_STRING) {
1378
            return false;
1379
        }
1380
1381
        // Check for older PHP, PHPCS version so we can compensate for misidentified tokens.
1382
        if (isset($isLowPHPCS, $isLowPHP) === false) {
1383
            $isLowPHP   = false;
1384
            $isLowPHPCS = false;
1385
            if (version_compare(\PHP_VERSION_ID, '50400', '<')) {
1386
                $isLowPHP   = true;
1387
                $isLowPHPCS = version_compare(PHPCSHelper::getVersion(), '2.4.0', '<');
1388
            }
1389
        }
1390
1391
        $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
1392
        if ($next !== false
1393
            && ($tokens[$next]['code'] === \T_OPEN_PARENTHESIS
1394
                || $tokens[$next]['code'] === \T_DOUBLE_COLON)
1395
        ) {
1396
            // Function call or declaration.
1397
            return false;
1398
        }
1399
1400
        // Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a global constant.
1401
        $tokensToIgnore = array(
1402
            'T_NAMESPACE'       => true,
1403
            'T_USE'             => true,
1404
            'T_CLASS'           => true,
1405
            'T_TRAIT'           => true,
1406
            'T_INTERFACE'       => true,
1407
            'T_EXTENDS'         => true,
1408
            'T_IMPLEMENTS'      => true,
1409
            'T_NEW'             => true,
1410
            'T_FUNCTION'        => true,
1411
            'T_DOUBLE_COLON'    => true,
1412
            'T_OBJECT_OPERATOR' => true,
1413
            'T_INSTANCEOF'      => true,
1414
            'T_INSTEADOF'       => true,
1415
            'T_GOTO'            => true,
1416
            'T_AS'              => true,
1417
            'T_PUBLIC'          => true,
1418
            'T_PROTECTED'       => true,
1419
            'T_PRIVATE'         => true,
1420
        );
1421
1422
        $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
1423
        if ($prev !== false
1424
            && (isset($tokensToIgnore[$tokens[$prev]['type']]) === true
1425
                || ($tokens[$prev]['code'] === \T_STRING
1426
                    && (($isLowPHPCS === true
1427
                        && $tokens[$prev]['content'] === 'trait')
1428
                    || ($isLowPHP === true
1429
                        && $tokens[$prev]['content'] === 'insteadof'))))
1430
        ) {
1431
            // Not the use of a constant.
1432
            return false;
1433
        }
1434
1435
        if ($prev !== false
1436
            && $tokens[$prev]['code'] === \T_NS_SEPARATOR
1437
            && $tokens[($prev - 1)]['code'] === \T_STRING
1438
        ) {
1439
            // Namespaced constant of the same name.
1440
            return false;
1441
        }
1442
1443
        if ($prev !== false
1444
            && $tokens[$prev]['code'] === \T_CONST
1445
            && $this->isClassConstant($phpcsFile, $prev) === true
1446
        ) {
1447
            // Class constant declaration of the same name.
1448
            return false;
1449
        }
1450
1451
        /*
1452
         * Deal with a number of variations of use statements.
1453
         */
1454
        for ($i = $stackPtr; $i > 0; $i--) {
1455
            if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
1456
                break;
1457
            }
1458
        }
1459
1460
        $firstOnLine = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
1461
        if ($firstOnLine !== false && $tokens[$firstOnLine]['code'] === \T_USE) {
1462
            $nextOnLine = $phpcsFile->findNext(Tokens::$emptyTokens, ($firstOnLine + 1), null, true);
1463
            if ($nextOnLine !== false) {
1464
                if (($tokens[$nextOnLine]['code'] === \T_STRING && $tokens[$nextOnLine]['content'] === 'const')
1465
                    || $tokens[$nextOnLine]['code'] === \T_CONST // Happens in some PHPCS versions.
1466
                ) {
1467
                    $hasNsSep = $phpcsFile->findNext(\T_NS_SEPARATOR, ($nextOnLine + 1), $stackPtr);
1468
                    if ($hasNsSep !== false) {
1469
                        // Namespaced const (group) use statement.
1470
                        return false;
1471
                    }
1472
                } else {
1473
                    // Not a const use statement.
1474
                    return false;
1475
                }
1476
            }
1477
        }
1478
1479
        return true;
1480
    }
1481
1482
1483
    /**
1484
     * Determine whether the tokens between $start and $end together form a positive number
1485
     * as recognized by PHP.
1486
     *
1487
     * The outcome of this function is reliable for `true`, `false` should be regarded as
1488
     * "undetermined".
1489
     *
1490
     * Note: Zero is *not* regarded as a positive number.
1491
     *
1492
     * @since 8.2.0
1493
     *
1494
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1495
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1496
     *                                           token will be examined as part of the snippet.
1497
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1498
     *                                           token will be examined as part of the snippet.
1499
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1500
     *
1501
     * @return bool True if PHP would evaluate the snippet as a positive number.
1502
     *              False if not or if it could not be reliably determined
1503
     *              (variable or calculations and such).
1504
     */
1505 View Code Duplication
    public function isPositiveNumber(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...
1506
    {
1507
        $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
1508
1509
        if ($number === false) {
1510
            return false;
1511
        }
1512
1513
        return ($number > 0);
1514
    }
1515
1516
1517
    /**
1518
     * Determine whether the tokens between $start and $end together form a negative number
1519
     * as recognized by PHP.
1520
     *
1521
     * The outcome of this function is reliable for `true`, `false` should be regarded as
1522
     * "undetermined".
1523
     *
1524
     * Note: Zero is *not* regarded as a negative number.
1525
     *
1526
     * @since 8.2.0
1527
     *
1528
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1529
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1530
     *                                           token will be examined as part of the snippet.
1531
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1532
     *                                           token will be examined as part of the snippet.
1533
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1534
     *
1535
     * @return bool True if PHP would evaluate the snippet as a negative number.
1536
     *              False if not or if it could not be reliably determined
1537
     *              (variable or calculations and such).
1538
     */
1539 View Code Duplication
    public function isNegativeNumber(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...
1540
    {
1541
        $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
1542
1543
        if ($number === false) {
1544
            return false;
1545
        }
1546
1547
        return ($number < 0);
1548
    }
1549
1550
    /**
1551
     * Determine whether the tokens between $start and $end together form a number
1552
     * as recognized by PHP.
1553
     *
1554
     * The outcome of this function is reliable for "true-ish" values, `false` should
1555
     * be regarded as "undetermined".
1556
     *
1557
     * @link https://3v4l.org/npTeM
1558
     *
1559
     * Mainly intended for examining variable assignments, function call parameters, array values
1560
     * where the start and end of the snippet to examine is very clear.
1561
     *
1562
     * @since 8.2.0
1563
     *
1564
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1565
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1566
     *                                           token will be examined as part of the snippet.
1567
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1568
     *                                           token will be examined as part of the snippet.
1569
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1570
     *
1571
     * @return int|float|bool The number found if PHP would evaluate the snippet as a number.
1572
     *                        The return type will be int if $allowFloats is false, if
1573
     *                        $allowFloats is true, the return type will be float.
1574
     *                        False will be returned when the snippet does not evaluate to a
1575
     *                        number or if it could not be reliably determined
1576
     *                        (variable or calculations and such).
1577
     */
1578
    protected function isNumber(File $phpcsFile, $start, $end, $allowFloats = false)
1579
    {
1580
        $stringTokens = Tokens::$heredocTokens + Tokens::$stringTokens;
1581
1582
        $validTokens             = array();
1583
        $validTokens[\T_LNUMBER] = true;
1584
        $validTokens[\T_TRUE]    = true; // Evaluates to int 1.
1585
        $validTokens[\T_FALSE]   = true; // Evaluates to int 0.
1586
        $validTokens[\T_NULL]    = true; // Evaluates to int 0.
1587
1588
        if ($allowFloats === true) {
1589
            $validTokens[\T_DNUMBER] = true;
1590
        }
1591
1592
        $maybeValidTokens = $stringTokens + $validTokens;
1593
1594
        $tokens         = $phpcsFile->getTokens();
1595
        $searchEnd      = ($end + 1);
1596
        $negativeNumber = false;
1597
1598
        if (isset($tokens[$start], $tokens[$searchEnd]) === false) {
1599
            return false;
1600
        }
1601
1602
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $searchEnd, true);
1603
        while ($nextNonEmpty !== false
1604
            && ($tokens[$nextNonEmpty]['code'] === \T_PLUS
1605
            || $tokens[$nextNonEmpty]['code'] === \T_MINUS)
1606
        ) {
1607
1608
            if ($tokens[$nextNonEmpty]['code'] === \T_MINUS) {
1609
                $negativeNumber = ($negativeNumber === false) ? true : false;
1610
            }
1611
1612
            $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
1613
        }
1614
1615
        if ($nextNonEmpty === false || isset($maybeValidTokens[$tokens[$nextNonEmpty]['code']]) === false) {
1616
            return false;
1617
        }
1618
1619
        $content = false;
1620
        if ($tokens[$nextNonEmpty]['code'] === \T_LNUMBER
1621
            || $tokens[$nextNonEmpty]['code'] === \T_DNUMBER
1622
        ) {
1623
            $content = (float) $tokens[$nextNonEmpty]['content'];
1624
        } elseif ($tokens[$nextNonEmpty]['code'] === \T_TRUE) {
1625
            $content = 1.0;
1626
        } elseif ($tokens[$nextNonEmpty]['code'] === \T_FALSE
1627
            || $tokens[$nextNonEmpty]['code'] === \T_NULL
1628
        ) {
1629
            $content = 0.0;
1630
        } elseif (isset($stringTokens[$tokens[$nextNonEmpty]['code']]) === true) {
1631
1632
            if ($tokens[$nextNonEmpty]['code'] === \T_START_HEREDOC
1633
                || $tokens[$nextNonEmpty]['code'] === \T_START_NOWDOC
1634
            ) {
1635
                // Skip past heredoc/nowdoc opener to the first content.
1636
                $firstDocToken = $phpcsFile->findNext(array(\T_HEREDOC, \T_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
1637
                if ($firstDocToken === false) {
1638
                    // Live coding or parse error.
1639
                    return false;
1640
                }
1641
1642
                $stringContent = $content = $tokens[$firstDocToken]['content'];
1643
1644
                // Skip forward to the end in preparation for the next part of the examination.
1645
                $nextNonEmpty = $phpcsFile->findNext(array(\T_END_HEREDOC, \T_END_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
1646
                if ($nextNonEmpty === false) {
1647
                    // Live coding or parse error.
1648
                    return false;
1649
                }
1650
            } else {
1651
                // Gather subsequent lines for a multi-line string.
1652
                for ($i = $nextNonEmpty; $i < $searchEnd; $i++) {
1653
                    if ($tokens[$i]['code'] !== $tokens[$nextNonEmpty]['code']) {
1654
                        break;
1655
                    }
1656
                    $content .= $tokens[$i]['content'];
1657
                }
1658
1659
                $nextNonEmpty  = --$i;
1660
                $content       = $this->stripQuotes($content);
1661
                $stringContent = $content;
1662
            }
1663
1664
            /*
1665
             * Regexes based on the formats outlined in the manual, created by JRF.
1666
             * @link https://www.php.net/manual/en/language.types.float.php
1667
             */
1668
            $regexInt   = '`^\s*[0-9]+`';
1669
            $regexFloat = '`^\s*(?:[+-]?(?:(?:(?P<LNUM>[0-9]+)|(?P<DNUM>([0-9]*\.(?P>LNUM)|(?P>LNUM)\.[0-9]*)))[eE][+-]?(?P>LNUM))|(?P>DNUM))`';
1670
1671
            $intString   = preg_match($regexInt, $content, $intMatch);
1672
            $floatString = preg_match($regexFloat, $content, $floatMatch);
1673
1674
            // Does the text string start with a number ? If so, PHP would juggle it and use it as a number.
1675
            if ($allowFloats === false) {
1676
                if ($intString !== 1 || $floatString === 1) {
1677
                    if ($floatString === 1) {
1678
                        // Found float. Only integers targetted.
1679
                        return false;
1680
                    }
1681
1682
                    $content = 0.0;
1683
                } else {
1684
                    $content = (float) trim($intMatch[0]);
1685
                }
1686
            } else {
1687
                if ($intString !== 1 && $floatString !== 1) {
1688
                    $content = 0.0;
1689
                } else {
1690
                    $content = ($floatString === 1) ? (float) trim($floatMatch[0]) : (float) trim($intMatch[0]);
1691
                }
1692
            }
1693
1694
            // Allow for different behaviour for hex numeric strings between PHP 5 vs PHP 7.
1695
            if ($intString === 1 && trim($intMatch[0]) === '0'
1696
                && preg_match('`^\s*(0x[A-Fa-f0-9]+)`', $stringContent, $hexNumberString) === 1
1697
                && $this->supportsBelow('5.6') === true
1698
            ) {
1699
                // The filter extension still allows for hex numeric strings in PHP 7, so
1700
                // use that to get the numeric value if possible.
1701
                // If the filter extension is not available, the value will be zero, but so be it.
1702
                if (function_exists('filter_var')) {
1703
                    $filtered = filter_var($hexNumberString[1], \FILTER_VALIDATE_INT, \FILTER_FLAG_ALLOW_HEX);
1704
                    if ($filtered !== false) {
1705
                        $content = $filtered;
1706
                    }
1707
                }
1708
            }
1709
        }
1710
1711
        // OK, so we have a number, now is there still more code after it ?
1712
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
1713
        if ($nextNonEmpty !== false) {
1714
            return false;
1715
        }
1716
1717
        if ($negativeNumber === true) {
1718
            $content = -$content;
1719
        }
1720
1721
        if ($allowFloats === false) {
1722
            return (int) $content;
1723
        }
1724
1725
        return $content;
1726
    }
1727
1728
1729
    /**
1730
     * Determine whether the tokens between $start and $end together form a numberic calculation
1731
     * as recognized by PHP.
1732
     *
1733
     * The outcome of this function is reliable for `true`, `false` should be regarded as "undetermined".
1734
     *
1735
     * Mainly intended for examining variable assignments, function call parameters, array values
1736
     * where the start and end of the snippet to examine is very clear.
1737
     *
1738
     * @since 9.0.0
1739
     *
1740
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1741
     * @param int                   $start     Start of the snippet (inclusive), i.e. this
1742
     *                                         token will be examined as part of the snippet.
1743
     * @param int                   $end       End of the snippet (inclusive), i.e. this
1744
     *                                         token will be examined as part of the snippet.
1745
     *
1746
     * @return bool
1747
     */
1748
    protected function isNumericCalculation(File $phpcsFile, $start, $end)
1749
    {
1750
        $arithmeticTokens = Tokens::$arithmeticTokens;
1751
1752
        // phpcs:disable PHPCompatibility.Constants.NewConstants.t_powFound
1753
        if (\defined('T_POW') && isset($arithmeticTokens[\T_POW]) === false) {
1754
            // T_POW was not added to the arithmetic array until PHPCS 2.9.0.
1755
            $arithmeticTokens[\T_POW] = \T_POW;
1756
        }
1757
        // phpcs:enable
1758
1759
        $skipTokens   = Tokens::$emptyTokens;
1760
        $skipTokens[] = \T_MINUS;
1761
        $skipTokens[] = \T_PLUS;
1762
1763
        // Find the first arithmetic operator, but skip past +/- signs before numbers.
1764
        $nextNonEmpty = ($start - 1);
1765 View Code Duplication
        do {
0 ignored issues
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...
1766
            $nextNonEmpty       = $phpcsFile->findNext($skipTokens, ($nextNonEmpty + 1), ($end + 1), true);
1767
            $arithmeticOperator = $phpcsFile->findNext($arithmeticTokens, ($nextNonEmpty + 1), ($end + 1));
1768
        } while ($nextNonEmpty !== false && $arithmeticOperator !== false && $nextNonEmpty === $arithmeticOperator);
1769
1770
        if ($arithmeticOperator === false) {
1771
            return false;
1772
        }
1773
1774
        $tokens      = $phpcsFile->getTokens();
1775
        $subsetStart = $start;
1776
        $subsetEnd   = ($arithmeticOperator - 1);
1777
1778
        while ($this->isNumber($phpcsFile, $subsetStart, $subsetEnd, true) !== false
1779
            && isset($tokens[($arithmeticOperator + 1)]) === true
1780
        ) {
1781
            // Recognize T_POW for PHPCS < 2.4.0 on low PHP versions.
1782
            if (\defined('T_POW') === false
1783
                && $tokens[$arithmeticOperator]['code'] === \T_MULTIPLY
1784
                && $tokens[($arithmeticOperator + 1)]['code'] === \T_MULTIPLY
1785
                && isset($tokens[$arithmeticOperator + 2]) === true
1786
            ) {
1787
                // Move operator one forward to the second * in T_POW.
1788
                ++$arithmeticOperator;
1789
            }
1790
1791
            $subsetStart  = ($arithmeticOperator + 1);
1792
            $nextNonEmpty = $arithmeticOperator;
1793 View Code Duplication
            do {
0 ignored issues
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...
1794
                $nextNonEmpty       = $phpcsFile->findNext($skipTokens, ($nextNonEmpty + 1), ($end + 1), true);
1795
                $arithmeticOperator = $phpcsFile->findNext($arithmeticTokens, ($nextNonEmpty + 1), ($end + 1));
1796
            } while ($nextNonEmpty !== false && $arithmeticOperator !== false && $nextNonEmpty === $arithmeticOperator);
1797
1798
            if ($arithmeticOperator === false) {
1799
                // Last calculation operator already reached.
1800
                if ($this->isNumber($phpcsFile, $subsetStart, $end, true) !== false) {
1801
                    return true;
1802
                }
1803
1804
                return false;
1805
            }
1806
1807
            $subsetEnd = ($arithmeticOperator - 1);
1808
        }
1809
1810
        return false;
1811
    }
1812
1813
1814
1815
    /**
1816
     * Determine whether a ternary is a short ternary, i.e. without "middle".
1817
     *
1818
     * N.B.: This is a back-fill for a new method which is expected to go into
1819
     * PHP_CodeSniffer 3.5.0.
1820
     * Once that method has been merged into PHPCS, this one should be moved
1821
     * to the PHPCSHelper.php file.
1822
     *
1823
     * @since 9.2.0
1824
     *
1825
     * @codeCoverageIgnore Method as pulled upstream is accompanied by unit tests.
1826
     *
1827
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1828
     * @param int                   $stackPtr  The position of the ternary operator
1829
     *                                         in the stack.
1830
     *
1831
     * @return bool True if short ternary, or false otherwise.
1832
     */
1833
    public function isShortTernary(File $phpcsFile, $stackPtr)
1834
    {
1835
        $tokens = $phpcsFile->getTokens();
1836 View Code Duplication
        if (isset($tokens[$stackPtr]) === false
0 ignored issues
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...
1837
            || $tokens[$stackPtr]['code'] !== \T_INLINE_THEN
1838
        ) {
1839
            return false;
1840
        }
1841
1842
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
1843
        if ($nextNonEmpty === false) {
1844
            // Live coding or parse error.
1845
            return false;
1846
        }
1847
1848
        if ($tokens[$nextNonEmpty]['code'] === \T_INLINE_ELSE) {
1849
            return true;
1850
        }
1851
1852
        return false;
1853
    }
1854
1855
1856
    /**
1857
     * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a list() construct.
1858
     *
1859
     * Note: A variety of PHPCS versions have bugs in the tokenizing of short arrays.
1860
     * In that case, the tokens are identified as T_OPEN/CLOSE_SQUARE_BRACKET.
1861
     *
1862
     * @since 8.2.0
1863
     *
1864
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1865
     * @param int                   $stackPtr  The position of the function call token.
1866
     *
1867
     * @return bool
1868
     */
1869
    public function isShortList(File $phpcsFile, $stackPtr)
1870
    {
1871
        $tokens = $phpcsFile->getTokens();
1872
1873
        // Check for the existence of the token.
1874
        if (isset($tokens[$stackPtr]) === false) {
1875
            return false;
1876
        }
1877
1878
        // Is this one of the tokens this function handles ?
1879
        if ($tokens[$stackPtr]['code'] !== \T_OPEN_SHORT_ARRAY
1880
            && $tokens[$stackPtr]['code'] !== \T_CLOSE_SHORT_ARRAY
1881
        ) {
1882
            return false;
1883
        }
1884
1885
        switch ($tokens[$stackPtr]['code']) {
1886
            case \T_OPEN_SHORT_ARRAY:
1887
                if (isset($tokens[$stackPtr]['bracket_closer']) === true) {
1888
                    $opener = $stackPtr;
1889
                    $closer = $tokens[$stackPtr]['bracket_closer'];
1890
                }
1891
                break;
1892
1893
            case \T_CLOSE_SHORT_ARRAY:
1894
                if (isset($tokens[$stackPtr]['bracket_opener']) === true) {
1895
                    $opener = $tokens[$stackPtr]['bracket_opener'];
1896
                    $closer = $stackPtr;
1897
                }
1898
                break;
1899
        }
1900
1901
        if (isset($opener, $closer) === false) {
1902
            // Parse error, live coding or real square bracket.
1903
            return false;
1904
        }
1905
1906
        /*
1907
         * PHPCS cross-version compatibility: work around for square brackets misidentified
1908
         * as short array when preceded by a variable variable in older PHPCS versions.
1909
         */
1910
        $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true, null, true);
1911
1912
        if ($prevNonEmpty !== false
1913
            && $tokens[$prevNonEmpty]['code'] === \T_CLOSE_CURLY_BRACKET
1914
            && isset($tokens[$prevNonEmpty]['bracket_opener']) === true
1915
        ) {
1916
            $maybeVariableVariable = $phpcsFile->findPrevious(
1917
                Tokens::$emptyTokens,
1918
                ($tokens[$prevNonEmpty]['bracket_opener'] - 1),
1919
                null,
1920
                true,
1921
                null,
1922
                true
1923
            );
1924
1925
            if ($tokens[$maybeVariableVariable]['code'] === \T_VARIABLE
1926
                || $tokens[$maybeVariableVariable]['code'] === \T_DOLLAR
1927
            ) {
1928
                return false;
1929
            }
1930
        }
1931
1932
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closer + 1), null, true, null, true);
1933
1934 View Code Duplication
        if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_EQUAL) {
0 ignored issues
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...
1935
            return true;
1936
        }
1937
1938
        if ($prevNonEmpty !== false
1939
            && $tokens[$prevNonEmpty]['code'] === \T_AS
1940
            && isset($tokens[$prevNonEmpty]['nested_parenthesis']) === true
1941
        ) {
1942
            $parentheses = array_reverse($tokens[$prevNonEmpty]['nested_parenthesis'], true);
1943 View Code Duplication
            foreach ($parentheses as $open => $close) {
0 ignored issues
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...
1944
                if (isset($tokens[$open]['parenthesis_owner'])
1945
                    && $tokens[$tokens[$open]['parenthesis_owner']]['code'] === \T_FOREACH
1946
                ) {
1947
                    return true;
1948
                }
1949
            }
1950
        }
1951
1952
        // Maybe this is a short list syntax nested inside another short list syntax ?
1953
        $parentOpener = $opener;
1954
        do {
1955
            $parentOpener = $phpcsFile->findPrevious(
1956
                array(\T_OPEN_SHORT_ARRAY, \T_OPEN_SQUARE_BRACKET),
1957
                ($parentOpener - 1),
1958
                null,
1959
                false,
1960
                null,
1961
                true
1962
            );
1963
1964
            if ($parentOpener === false) {
1965
                return false;
1966
            }
1967
1968
        } while (isset($tokens[$parentOpener]['bracket_closer']) === true
1969
            && $tokens[$parentOpener]['bracket_closer'] < $opener
1970
        );
1971
1972
        if (isset($tokens[$parentOpener]['bracket_closer']) === true
1973
            && $tokens[$parentOpener]['bracket_closer'] > $closer
1974
        ) {
1975
            // Work around tokenizer issue in PHPCS 2.0 - 2.7.
1976
            $phpcsVersion = PHPCSHelper::getVersion();
1977
            if ((version_compare($phpcsVersion, '2.0', '>') === true
1978
                && version_compare($phpcsVersion, '2.8', '<') === true)
1979
                && $tokens[$parentOpener]['code'] === \T_OPEN_SQUARE_BRACKET
1980
            ) {
1981
                $nextNonEmpty = $phpcsFile->findNext(
1982
                    Tokens::$emptyTokens,
1983
                    ($tokens[$parentOpener]['bracket_closer'] + 1),
1984
                    null,
1985
                    true,
1986
                    null,
1987
                    true
1988
                );
1989
1990 View Code Duplication
                if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_EQUAL) {
0 ignored issues
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...
1991
                    return true;
1992
                }
1993
1994
                return false;
1995
            }
1996
1997
            return $this->isShortList($phpcsFile, $parentOpener);
1998
        }
1999
2000
        return false;
2001
    }
2002
2003
2004
    /**
2005
     * Determine whether the tokens between $start and $end could together represent a variable.
2006
     *
2007
     * @since 9.0.0
2008
     *
2009
     * @param \PHP_CodeSniffer_File $phpcsFile          The file being scanned.
2010
     * @param int                   $start              Starting point stack pointer. Inclusive.
2011
     *                                                  I.e. this token should be taken into account.
2012
     * @param int                   $end                End point stack pointer. Exclusive.
2013
     *                                                  I.e. this token should not be taken into account.
2014
     * @param int                   $targetNestingLevel The nesting level the variable should be at.
2015
     *
2016
     * @return bool
2017
     */
2018
    public function isVariable(File $phpcsFile, $start, $end, $targetNestingLevel)
2019
    {
2020
        static $tokenBlackList, $bracketTokens;
2021
2022
        // Create the token arrays only once.
2023
        if (isset($tokenBlackList, $bracketTokens) === false) {
2024
2025
            $tokenBlackList  = array(
2026
                \T_OPEN_PARENTHESIS => \T_OPEN_PARENTHESIS,
2027
                \T_STRING_CONCAT    => \T_STRING_CONCAT,
2028
            );
2029
            $tokenBlackList += Tokens::$assignmentTokens;
2030
            $tokenBlackList += Tokens::$equalityTokens;
2031
            $tokenBlackList += Tokens::$comparisonTokens;
2032
            $tokenBlackList += Tokens::$operators;
2033
            $tokenBlackList += Tokens::$booleanOperators;
2034
            $tokenBlackList += Tokens::$castTokens;
2035
2036
            /*
2037
             * List of brackets which can be part of a variable variable.
2038
             *
2039
             * Key is the open bracket token, value the close bracket token.
2040
             */
2041
            $bracketTokens = array(
2042
                \T_OPEN_CURLY_BRACKET  => \T_CLOSE_CURLY_BRACKET,
2043
                \T_OPEN_SQUARE_BRACKET => \T_CLOSE_SQUARE_BRACKET,
2044
            );
2045
        }
2046
2047
        $tokens = $phpcsFile->getTokens();
2048
2049
        // If no variable at all was found, then it's definitely a no-no.
2050
        $hasVariable = $phpcsFile->findNext(\T_VARIABLE, $start, $end);
2051
        if ($hasVariable === false) {
2052
            return false;
2053
        }
2054
2055
        // Check if the variable found is at the right level. Deeper levels are always an error.
2056 View Code Duplication
        if (isset($tokens[$hasVariable]['nested_parenthesis'])
0 ignored issues
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...
2057
            && \count($tokens[$hasVariable]['nested_parenthesis']) !== $targetNestingLevel
2058
        ) {
2059
                return false;
2060
        }
2061
2062
        // Ok, so the first variable is at the right level, now are there any
2063
        // blacklisted tokens within the empty() ?
2064
        $hasBadToken = $phpcsFile->findNext($tokenBlackList, $start, $end);
2065
        if ($hasBadToken === false) {
2066
            return true;
2067
        }
2068
2069
        // If there are also bracket tokens, the blacklisted token might be part of a variable
2070
        // variable, but if there are no bracket tokens, we know we have an error.
2071
        $hasBrackets = $phpcsFile->findNext($bracketTokens, $start, $end);
2072
        if ($hasBrackets === false) {
2073
            return false;
2074
        }
2075
2076
        // Ok, we have both a blacklisted token as well as brackets, so we need to walk
2077
        // the tokens of the variable variable.
2078
        for ($i = $start; $i < $end; $i++) {
2079
            // If this is a bracket token, skip to the end of the bracketed expression.
2080
            if (isset($bracketTokens[$tokens[$i]['code']], $tokens[$i]['bracket_closer'])) {
2081
                $i = $tokens[$i]['bracket_closer'];
2082
                continue;
2083
            }
2084
2085
            // If it's a blacklisted token, not within brackets, we have an error.
2086
            if (isset($tokenBlackList[$tokens[$i]['code']])) {
2087
                return false;
2088
            }
2089
        }
2090
2091
        return true;
2092
    }
2093
2094
    /**
2095
     * Determine whether a T_MINUS/T_PLUS token is a unary operator.
2096
     *
2097
     * N.B.: This is a back-fill for a new method which is expected to go into
2098
     * PHP_CodeSniffer 3.5.0.
2099
     * Once that method has been merged into PHPCS, this one should be moved
2100
     * to the PHPCSHelper.php file.
2101
     *
2102
     * @since 9.2.0
2103
     *
2104
     * @codeCoverageIgnore Method as pulled upstream is accompanied by unit tests.
2105
     *
2106
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
2107
     * @param int                   $stackPtr  The position of the plus/minus token.
2108
     *
2109
     * @return bool True if the token passed is a unary operator.
2110
     *              False otherwise or if the token is not a T_PLUS/T_MINUS token.
2111
     */
2112
    public static function isUnaryPlusMinus(File $phpcsFile, $stackPtr)
2113
    {
2114
        $tokens = $phpcsFile->getTokens();
2115
2116
        if (isset($tokens[$stackPtr]) === false
2117
            || ($tokens[$stackPtr]['code'] !== \T_PLUS
2118
            && $tokens[$stackPtr]['code'] !== \T_MINUS)
2119
        ) {
2120
            return false;
2121
        }
2122
2123
        $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
2124
        if ($next === false) {
2125
            // Live coding or parse error.
2126
            return false;
2127
        }
2128
2129 View Code Duplication
        if (isset(Tokens::$operators[$tokens[$next]['code']]) === true) {
0 ignored issues
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...
2130
            // Next token is an operator, so this is not a unary.
2131
            return false;
2132
        }
2133
2134
        $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
2135
2136
        if ($tokens[$prev]['code'] === \T_RETURN) {
2137
            // Just returning a positive/negative value; eg. (return -1).
2138
            return true;
2139
        }
2140
2141 View Code Duplication
        if (isset(Tokens::$operators[$tokens[$prev]['code']]) === true) {
0 ignored issues
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...
2142
            // Just trying to operate on a positive/negative value; eg. ($var * -1).
2143
            return true;
2144
        }
2145
2146
        if (isset(Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true) {
2147
            // Just trying to compare a positive/negative value; eg. ($var === -1).
2148
            return true;
2149
        }
2150
2151
        if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) {
2152
            // Just trying to compare a positive/negative value; eg. ($var || -1 === $b).
2153
            return true;
2154
        }
2155
2156 View Code Duplication
        if (isset(Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true) {
0 ignored issues
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...
2157
            // Just trying to assign a positive/negative value; eg. ($var = -1).
2158
            return true;
2159
        }
2160
2161
        if (isset(Tokens::$castTokens[$tokens[$prev]['code']]) === true) {
2162
            // Just casting a positive/negative value; eg. (string) -$var.
2163
            return true;
2164
        }
2165
2166
        // Other indicators that a plus/minus sign is a unary operator.
2167
        $invalidTokens = array(
2168
            \T_COMMA               => true,
2169
            \T_OPEN_PARENTHESIS    => true,
2170
            \T_OPEN_SQUARE_BRACKET => true,
2171
            \T_OPEN_SHORT_ARRAY    => true,
2172
            \T_COLON               => true,
2173
            \T_INLINE_THEN         => true,
2174
            \T_INLINE_ELSE         => true,
2175
            \T_CASE                => true,
2176
            \T_OPEN_CURLY_BRACKET  => true,
2177
            \T_STRING_CONCAT       => true,
2178
        );
2179
2180
        if (isset($invalidTokens[$tokens[$prev]['code']]) === true) {
2181
            // Just trying to use a positive/negative value; eg. myFunction($var, -2).
2182
            return true;
2183
        }
2184
2185
        return false;
2186
    }
2187
2188
    /**
2189
     * Get the complete contents of a multi-line text string.
2190
     *
2191
     * N.B.: This is a back-fill for a new method which is expected to go into
2192
     * PHP_CodeSniffer 3.5.0.
2193
     * Once that method has been merged into PHPCS, this one should be moved
2194
     * to the PHPCSHelper.php file.
2195
     *
2196
     * @since 9.3.0
2197
     *
2198
     * @codeCoverageIgnore Method as pulled upstream is accompanied by unit tests.
2199
     *
2200
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
2201
     * @param int                   $stackPtr    Pointer to the first text string token
2202
     *                                           of a multi-line text string or to a
2203
     *                                           Nowdoc/Heredoc opener.
2204
     * @param bool                  $stripQuotes Optional. Whether to strip text delimiter
2205
     *                                           quotes off the resulting text string.
2206
     *                                           Defaults to true.
2207
     *
2208
     * @return string
2209
     *
2210
     * @throws \PHP_CodeSniffer_Exception If the specified position is not a
2211
     *                                    valid text string token or if the
2212
     *                                    token is not the first text string token.
2213
     */
2214
    public function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQuotes = true)
2215
    {
2216
        $tokens = $phpcsFile->getTokens();
2217
2218
        // Must be the start of a text string token.
2219
        if ($tokens[$stackPtr]['code'] !== \T_START_HEREDOC
2220
            && $tokens[$stackPtr]['code'] !== \T_START_NOWDOC
2221
            && $tokens[$stackPtr]['code'] !== \T_CONSTANT_ENCAPSED_STRING
2222
            && $tokens[$stackPtr]['code'] !== \T_DOUBLE_QUOTED_STRING
2223
        ) {
2224
            throw new PHPCS_Exception('$stackPtr must be of type T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING or T_DOUBLE_QUOTED_STRING');
2225
        }
2226
2227
        if ($tokens[$stackPtr]['code'] === \T_CONSTANT_ENCAPSED_STRING
2228
            || $tokens[$stackPtr]['code'] === \T_DOUBLE_QUOTED_STRING
2229
        ) {
2230
            $prev = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
2231
            if ($tokens[$stackPtr]['code'] === $tokens[$prev]['code']) {
2232
                throw new PHPCS_Exception('$stackPtr must be the start of the text string');
2233
            }
2234
        }
2235
2236
        switch ($tokens[$stackPtr]['code']) {
2237
            case \T_START_HEREDOC:
2238
                $stripQuotes = false;
2239
                $targetType  = \T_HEREDOC;
2240
                $current     = ($stackPtr + 1);
2241
                break;
2242
2243
            case \T_START_NOWDOC:
2244
                $stripQuotes = false;
2245
                $targetType  = \T_NOWDOC;
2246
                $current     = ($stackPtr + 1);
2247
                break;
2248
2249
            default:
2250
                $targetType = $tokens[$stackPtr]['code'];
2251
                $current    = $stackPtr;
2252
                break;
2253
        }
2254
2255
        $string = '';
2256
        do {
2257
            $string .= $tokens[$current]['content'];
2258
            ++$current;
2259
        } while ($tokens[$current]['code'] === $targetType);
2260
2261
        if ($stripQuotes === true) {
2262
            return $this->stripQuotes($string);
2263
        }
2264
2265
        return $string;
2266
    }
2267
}
2268