Completed
Push — feature/isClassProperty-additi... ( 286703...879b22 )
by Juliette
01:45
created

Sniff::isNumber()   F

Complexity

Conditions 35
Paths 1256

Size

Total Lines 143
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 143
rs 2
cc 35
eloc 77
nc 1256
nop 4

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * \PHPCompatibility\Sniff.
4
 *
5
 * @category  PHP
6
 * @package   PHPCompatibility
7
 * @author    Wim Godden <[email protected]>
8
 * @copyright 2014 Cu.be Solutions bvba
9
 */
10
11
namespace PHPCompatibility;
12
13
use PHPCompatibility\PHPCSHelper;
14
15
/**
16
 * \PHPCompatibility\Sniff.
17
 *
18
 * @category  PHP
19
 * @package   PHPCompatibility
20
 * @author    Wim Godden <[email protected]>
21
 * @copyright 2014 Cu.be Solutions bvba
22
 */
23
abstract class Sniff implements \PHP_CodeSniffer_Sniff
24
{
25
26
    const REGEX_COMPLEX_VARS = '`(?:(\{)?(?<!\\\\)\$)?(\{)?(?<!\\\\)\$(\{)?(?P<varname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:->\$?(?P>varname)|\[[^\]]+\]|::\$?(?P>varname)|\([^\)]*\))*(?(3)\}|)(?(2)\}|)(?(1)\}|)`';
27
28
    /**
29
     * List of superglobals as an array of strings.
30
     *
31
     * Used by the ParameterShadowSuperGlobals and ForbiddenClosureUseVariableNames sniffs.
32
     *
33
     * @var array
34
     */
35
    protected $superglobals = array(
36
        '$GLOBALS',
37
        '$_SERVER',
38
        '$_GET',
39
        '$_POST',
40
        '$_FILES',
41
        '$_COOKIE',
42
        '$_SESSION',
43
        '$_REQUEST',
44
        '$_ENV',
45
    );
46
47
    /**
48
     * List of functions using hash algorithm as parameter (always the first parameter).
49
     *
50
     * Used by the new/removed hash algorithm sniffs.
51
     * Key is the function name, value is the 1-based parameter position in the function call.
52
     *
53
     * @var array
54
     */
55
    protected $hashAlgoFunctions = array(
56
        'hash_file'      => 1,
57
        'hash_hmac_file' => 1,
58
        'hash_hmac'      => 1,
59
        'hash_init'      => 1,
60
        'hash_pbkdf2'    => 1,
61
        'hash'           => 1,
62
    );
63
64
65
    /**
66
     * List of functions which take an ini directive as parameter (always the first parameter).
67
     *
68
     * Used by the new/removed ini directives sniffs.
69
     * Key is the function name, value is the 1-based parameter position in the function call.
70
     *
71
     * @var array
72
     */
73
    protected $iniFunctions = array(
74
        'ini_get' => 1,
75
        'ini_set' => 1,
76
    );
77
78
79
    /**
80
     * Get the testVersion configuration variable.
81
     *
82
     * The testVersion configuration variable may be in any of the following formats:
83
     * 1) Omitted/empty, in which case no version is specified. This effectively
84
     *    disables all the checks for new PHP features provided by this standard.
85
     * 2) A single PHP version number, e.g. "5.4" in which case the standard checks that
86
     *    the code will run on that version of PHP (no deprecated features or newer
87
     *    features being used).
88
     * 3) A range, e.g. "5.0-5.5", in which case the standard checks the code will run
89
     *    on all PHP versions in that range, and that it doesn't use any features that
90
     *    were deprecated by the final version in the list, or which were not available
91
     *    for the first version in the list.
92
     *    We accept ranges where one of the components is missing, e.g. "-5.6" means
93
     *    all versions up to PHP 5.6, and "7.0-" means all versions above PHP 7.0.
94
     * PHP version numbers should always be in Major.Minor format.  Both "5", "5.3.2"
95
     * would be treated as invalid, and ignored.
96
     *
97
     * @return array $arrTestVersions will hold an array containing min/max version
98
     *               of PHP that we are checking against (see above).  If only a
99
     *               single version number is specified, then this is used as
100
     *               both the min and max.
101
     *
102
     * @throws \PHP_CodeSniffer_Exception If testVersion is invalid.
103
     */
104
    private function getTestVersion()
105
    {
106
        static $arrTestVersions = array();
107
108
        $default     = array(null, null);
109
        $testVersion = trim(PHPCSHelper::getConfigData('testVersion'));
110
111
        if (empty($testVersion) === false && isset($arrTestVersions[$testVersion]) === false) {
112
113
            $arrTestVersions[$testVersion] = $default;
114
115
            if (preg_match('`^\d+\.\d+$`', $testVersion)) {
116
                $arrTestVersions[$testVersion] = array($testVersion, $testVersion);
117
                return $arrTestVersions[$testVersion];
118
            }
119
120
            if (preg_match('`^(\d+\.\d+)?\s*-\s*(\d+\.\d+)?$`', $testVersion, $matches)) {
121
                if (empty($matches[1]) === false || empty($matches[2]) === false) {
122
                    // If no lower-limit is set, we set the min version to 4.0.
123
                    // Whilst development focuses on PHP 5 and above, we also accept
124
                    // sniffs for PHP 4, so we include that as the minimum.
125
                    // (It makes no sense to support PHP 3 as this was effectively a
126
                    // different language).
127
                    $min = empty($matches[1]) ? '4.0' : $matches[1];
128
129
                    // If no upper-limit is set, we set the max version to 99.9.
130
                    $max = empty($matches[2]) ? '99.9' : $matches[2];
131
132
                    if (version_compare($min, $max, '>')) {
133
                        trigger_error(
134
                            "Invalid range in testVersion setting: '" . $testVersion . "'",
135
                            E_USER_WARNING
136
                        );
137
                        return $default;
138
                    }
139
                    else {
140
                        $arrTestVersions[$testVersion] = array($min, $max);
141
                        return $arrTestVersions[$testVersion];
142
                    }
143
                }
144
            }
145
146
            trigger_error(
147
                "Invalid testVersion setting: '" . $testVersion . "'",
148
                E_USER_WARNING
149
            );
150
            return $default;
151
        }
152
153
        if (isset($arrTestVersions[$testVersion])) {
154
            return $arrTestVersions[$testVersion];
155
        }
156
157
        return $default;
158
    }
159
160
161
    /**
162
     * Check whether a specific PHP version is equal to or higher than the maximum
163
     * supported PHP version as provided by the user in `testVersion`.
164
     *
165
     * Should be used when sniffing for *old* PHP features (deprecated/removed).
166
     *
167
     * @param string $phpVersion A PHP version number in 'major.minor' format.
168
     *
169
     * @return bool True if testVersion has not been provided or if the PHP version
170
     *              is equal to or higher than the highest supported PHP version
171
     *              in testVersion. False otherwise.
172
     */
173 View Code Duplication
    public function supportsAbove($phpVersion)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
    {
175
        $testVersion = $this->getTestVersion();
176
        $testVersion = $testVersion[1];
177
178
        if (is_null($testVersion)
179
            || version_compare($testVersion, $phpVersion) >= 0
180
        ) {
181
            return true;
182
        } else {
183
            return false;
184
        }
185
    }//end supportsAbove()
186
187
188
    /**
189
     * Check whether a specific PHP version is equal to or lower than the minimum
190
     * supported PHP version as provided by the user in `testVersion`.
191
     *
192
     * Should be used when sniffing for *new* PHP features.
193
     *
194
     * @param string $phpVersion A PHP version number in 'major.minor' format.
195
     *
196
     * @return bool True if the PHP version is equal to or lower than the lowest
197
     *              supported PHP version in testVersion.
198
     *              False otherwise or if no testVersion is provided.
199
     */
200 View Code Duplication
    public function supportsBelow($phpVersion)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
201
    {
202
        $testVersion = $this->getTestVersion();
203
        $testVersion = $testVersion[0];
204
205
        if (is_null($testVersion) === false
206
            && version_compare($testVersion, $phpVersion) <= 0
207
        ) {
208
            return true;
209
        } else {
210
            return false;
211
        }
212
    }//end supportsBelow()
213
214
215
    /**
216
     * Add a PHPCS message to the output stack as either a warning or an error.
217
     *
218
     * @param \PHP_CodeSniffer_File $phpcsFile The file the message applies to.
219
     * @param string                $message   The message.
220
     * @param int                   $stackPtr  The position of the token
221
     *                                         the message relates to.
222
     * @param bool                  $isError   Whether to report the message as an
223
     *                                         'error' or 'warning'.
224
     *                                         Defaults to true (error).
225
     * @param string                $code      The error code for the message.
226
     *                                         Defaults to 'Found'.
227
     * @param array                 $data      Optional input for the data replacements.
228
     *
229
     * @return void
230
     */
231
    public function addMessage(\PHP_CodeSniffer_File $phpcsFile, $message, $stackPtr, $isError, $code = 'Found', $data = array())
232
    {
233
        if ($isError === true) {
234
            $phpcsFile->addError($message, $stackPtr, $code, $data);
235
        } else {
236
            $phpcsFile->addWarning($message, $stackPtr, $code, $data);
237
        }
238
    }
239
240
241
    /**
242
     * Convert an arbitrary string to an alphanumeric string with underscores.
243
     *
244
     * Pre-empt issues with arbitrary strings being used as error codes in XML and PHP.
245
     *
246
     * @param string $baseString Arbitrary string.
247
     *
248
     * @return string
249
     */
250
    public function stringToErrorCode($baseString)
251
    {
252
        return preg_replace('`[^a-z0-9_]`i', '_', strtolower($baseString));
253
    }
254
255
256
    /**
257
     * Strip quotes surrounding an arbitrary string.
258
     *
259
     * Intended for use with the content of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
260
     *
261
     * @param string $string The raw string.
262
     *
263
     * @return string String without quotes around it.
264
     */
265
    public function stripQuotes($string)
266
    {
267
        return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
268
    }
269
270
271
    /**
272
     * Strip variables from an arbitrary double quoted string.
273
     *
274
     * Intended for use with the content of a T_DOUBLE_QUOTED_STRING.
275
     *
276
     * @param string $string The raw string.
277
     *
278
     * @return string String without variables in it.
279
     */
280
    public function stripVariables($string)
281
    {
282
        if (strpos($string, '$') === false) {
283
            return $string;
284
        }
285
286
        return preg_replace(self::REGEX_COMPLEX_VARS, '', $string);
287
    }
288
289
290
    /**
291
     * Make all top level array keys in an array lowercase.
292
     *
293
     * @param array $array Initial array.
294
     *
295
     * @return array Same array, but with all lowercase top level keys.
296
     */
297
    public function arrayKeysToLowercase($array)
298
    {
299
        $keys = array_keys($array);
300
        $keys = array_map('strtolower', $keys);
301
        return array_combine($keys, $array);
302
    }
303
304
305
    /**
306
     * Checks if a function call has parameters.
307
     *
308
     * Expects to be passed the T_STRING stack pointer for the function call.
309
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
310
     *
311
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it
312
     * will detect whether the array has values or is empty.
313
     *
314
     * @link https://github.com/wimg/PHPCompatibility/issues/120
315
     * @link https://github.com/wimg/PHPCompatibility/issues/152
316
     *
317
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
318
     * @param int                   $stackPtr  The position of the function call token.
319
     *
320
     * @return bool
321
     */
322
    public function doesFunctionCallHaveParameters(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
323
    {
324
        $tokens = $phpcsFile->getTokens();
325
326
        // Check for the existence of the token.
327
        if (isset($tokens[$stackPtr]) === false) {
328
            return false;
329
        }
330
331
        // Is this one of the tokens this function handles ?
332
        if (in_array($tokens[$stackPtr]['code'], array(T_STRING, T_ARRAY, T_OPEN_SHORT_ARRAY), true) === false) {
333
            return false;
334
        }
335
336
        $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
337
338
        // Deal with short array syntax.
339
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) {
340
            if (isset($tokens[$stackPtr]['bracket_closer']) === false) {
341
                return false;
342
            }
343
344
            if ($nextNonEmpty === $tokens[$stackPtr]['bracket_closer']) {
345
                // No parameters.
346
                return false;
347
            } else {
348
                return true;
349
            }
350
        }
351
352
        // Deal with function calls & long arrays.
353
        // Next non-empty token should be the open parenthesis.
354
        if ($nextNonEmpty === false && $tokens[$nextNonEmpty]['code'] !== T_OPEN_PARENTHESIS) {
355
            return false;
356
        }
357
358
        if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
359
            return false;
360
        }
361
362
        $closeParenthesis = $tokens[$nextNonEmpty]['parenthesis_closer'];
363
        $nextNextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $nextNonEmpty + 1, $closeParenthesis + 1, true);
364
365
        if ($nextNextNonEmpty === $closeParenthesis) {
366
            // No parameters.
367
            return false;
368
        }
369
370
        return true;
371
    }
372
373
374
    /**
375
     * Count the number of parameters a function call has been passed.
376
     *
377
     * Expects to be passed the T_STRING stack pointer for the function call.
378
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
379
     *
380
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
381
     * it will return the number of values in the array.
382
     *
383
     * @link https://github.com/wimg/PHPCompatibility/issues/111
384
     * @link https://github.com/wimg/PHPCompatibility/issues/114
385
     * @link https://github.com/wimg/PHPCompatibility/issues/151
386
     *
387
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
388
     * @param int                   $stackPtr  The position of the function call token.
389
     *
390
     * @return int
391
     */
392
    public function getFunctionCallParameterCount(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
393
    {
394
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
395
            return 0;
396
        }
397
398
        return count($this->getFunctionCallParameters($phpcsFile, $stackPtr));
399
    }
400
401
402
    /**
403
     * Get information on all parameters passed to a function call.
404
     *
405
     * Expects to be passed the T_STRING stack pointer for the function call.
406
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
407
     *
408
     * Will return an multi-dimentional array with the start token pointer, end token
409
     * pointer and raw parameter value for all parameters. Index will be 1-based.
410
     * If no parameters are found, will return an empty array.
411
     *
412
     * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
413
     * it will tokenize the values / key/value pairs contained in the array call.
414
     *
415
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
416
     * @param int                   $stackPtr  The position of the function call token.
417
     *
418
     * @return array
419
     */
420
    public function getFunctionCallParameters(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
421
    {
422
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
423
            return array();
424
        }
425
426
        // Ok, we know we have a T_STRING, T_ARRAY or T_OPEN_SHORT_ARRAY with parameters
427
        // and valid open & close brackets/parenthesis.
428
        $tokens = $phpcsFile->getTokens();
429
430
        // Mark the beginning and end tokens.
431
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) {
432
            $opener = $stackPtr;
433
            $closer = $tokens[$stackPtr]['bracket_closer'];
434
435
            $nestedParenthesisCount = 0;
436
437
        } else {
438
            $opener = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
439
            $closer = $tokens[$opener]['parenthesis_closer'];
440
441
            $nestedParenthesisCount = 1;
442
        }
443
444
        // Which nesting level is the one we are interested in ?
445 View Code Duplication
        if (isset($tokens[$opener]['nested_parenthesis'])) {
446
            $nestedParenthesisCount += count($tokens[$opener]['nested_parenthesis']);
447
        }
448
449
        $parameters = array();
450
        $nextComma  = $opener;
451
        $paramStart = $opener + 1;
452
        $cnt        = 1;
453
        while (($nextComma = $phpcsFile->findNext(array(T_COMMA, $tokens[$closer]['code'], T_OPEN_SHORT_ARRAY), $nextComma + 1, $closer + 1)) !== false) {
454
            // Ignore anything within short array definition brackets.
455
            if ($tokens[$nextComma]['type'] === 'T_OPEN_SHORT_ARRAY'
456
                && (isset($tokens[$nextComma]['bracket_opener'])
457
                    && $tokens[$nextComma]['bracket_opener'] === $nextComma)
458
                && isset($tokens[$nextComma]['bracket_closer'])
459
            ) {
460
                // Skip forward to the end of the short array definition.
461
                $nextComma = $tokens[$nextComma]['bracket_closer'];
462
                continue;
463
            }
464
465
            // Ignore comma's at a lower nesting level.
466
            if ($tokens[$nextComma]['type'] === 'T_COMMA'
467
                && isset($tokens[$nextComma]['nested_parenthesis'])
468
                && count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount
469
            ) {
470
                continue;
471
            }
472
473
            // Ignore closing parenthesis/bracket if not 'ours'.
474
            if ($tokens[$nextComma]['type'] === $tokens[$closer]['type'] && $nextComma !== $closer) {
475
                continue;
476
            }
477
478
            // Ok, we've reached the end of the parameter.
479
            $parameters[$cnt]['start'] = $paramStart;
480
            $parameters[$cnt]['end']   = $nextComma - 1;
481
            $parameters[$cnt]['raw']   = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart)));
482
483
            // Check if there are more tokens before the closing parenthesis.
484
            // Prevents code like the following from setting a third parameter:
485
            // functionCall( $param1, $param2, );
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
486
            $hasNextParam = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $nextComma + 1, $closer, true, null, true);
487
            if ($hasNextParam === false) {
488
                break;
489
            }
490
491
            // Prepare for the next parameter.
492
            $paramStart = $nextComma + 1;
493
            $cnt++;
494
        }
495
496
        return $parameters;
497
    }
498
499
500
    /**
501
     * Get information on a specific parameter passed to a function call.
502
     *
503
     * Expects to be passed the T_STRING stack pointer for the function call.
504
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
505
     *
506
     * Will return a array with the start token pointer, end token pointer and the raw value
507
     * of the parameter at a specific offset.
508
     * If the specified parameter is not found, will return false.
509
     *
510
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
511
     * @param int                   $stackPtr    The position of the function call token.
512
     * @param int                   $paramOffset The 1-based index position of the parameter to retrieve.
513
     *
514
     * @return array|false
515
     */
516
    public function getFunctionCallParameter(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $paramOffset)
517
    {
518
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
519
520
        if (isset($parameters[$paramOffset]) === false) {
521
            return false;
522
        } else {
523
            return $parameters[$paramOffset];
524
        }
525
    }
526
527
528
    /**
529
     * Verify whether a token is within a scoped condition.
530
     *
531
     * If the optional $validScopes parameter has been passed, the function
532
     * will check that the token has at least one condition which is of a
533
     * type defined in $validScopes.
534
     *
535
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
536
     * @param int                   $stackPtr    The position of the token.
537
     * @param array|int             $validScopes Optional. Array of valid scopes
538
     *                                           or int value of a valid scope.
539
     *                                           Pass the T_.. constant(s) for the
540
     *                                           desired scope to this parameter.
541
     *
542
     * @return bool Without the optional $scopeTypes: True if within a scope, false otherwise.
543
     *              If the $scopeTypes are set: True if *one* of the conditions is a
544
     *              valid scope, false otherwise.
545
     */
546
    public function tokenHasScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $validScopes = null)
547
    {
548
        $tokens = $phpcsFile->getTokens();
549
550
        // Check for the existence of the token.
551
        if (isset($tokens[$stackPtr]) === false) {
552
            return false;
553
        }
554
555
        // No conditions = no scope.
556
        if (empty($tokens[$stackPtr]['conditions'])) {
557
            return false;
558
        }
559
560
        // Ok, there are conditions, do we have to check for specific ones ?
561
        if (isset($validScopes) === false) {
562
            return true;
563
        }
564
565
        return $phpcsFile->hasCondition($stackPtr, $validScopes);
566
    }
567
568
569
    /**
570
     * Verify whether a token is within a class scope.
571
     *
572
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
573
     * @param int                   $stackPtr  The position of the token.
574
     * @param bool                  $strict    Whether to strictly check for the T_CLASS
575
     *                                         scope or also accept interfaces and traits
576
     *                                         as scope.
577
     *
578
     * @return bool True if within class scope, false otherwise.
579
     */
580
    public function inClassScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $strict = true)
581
    {
582
        $validScopes = array(T_CLASS);
583
        if (defined('T_ANON_CLASS') === true) {
584
            $validScopes[] = T_ANON_CLASS;
585
        }
586
587
        if ($strict === false) {
588
            $validScopes[] = T_INTERFACE;
589
590
            if (defined('T_TRAIT')) {
591
                // phpcs:ignore PHPCompatibility.PHP.NewConstants.t_traitFound
592
                $validScopes[] = T_TRAIT;
593
            }
594
        }
595
596
        return $phpcsFile->hasCondition($stackPtr, $validScopes);
597
    }
598
599
600
    /**
601
     * Verify whether a token is within a scoped use statement.
602
     *
603
     * PHPCS cross-version compatibility method.
604
     *
605
     * In PHPCS 1.x no conditions are set for a scoped use statement.
606
     * This method works around that limitation.
607
     *
608
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
609
     * @param int                   $stackPtr  The position of the token.
610
     *
611
     * @return bool True if within use scope, false otherwise.
612
     */
613
    public function inUseScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
614
    {
615
        static $isLowPHPCS, $ignoreTokens;
616
617
        if (isset($isLowPHPCS) === false) {
618
            $isLowPHPCS = version_compare(PHPCSHelper::getVersion(), '2.3.0', '<');
619
        }
620
        if (isset($ignoreTokens) === false) {
621
            $ignoreTokens              = \PHP_CodeSniffer_Tokens::$emptyTokens;
622
            $ignoreTokens[T_STRING]    = T_STRING;
623
            $ignoreTokens[T_AS]        = T_AS;
624
            $ignoreTokens[T_PUBLIC]    = T_PUBLIC;
625
            $ignoreTokens[T_PROTECTED] = T_PROTECTED;
626
            $ignoreTokens[T_PRIVATE]   = T_PRIVATE;
627
        }
628
629
        // PHPCS 2.0.
630
        if ($isLowPHPCS === false) {
631
            return $phpcsFile->hasCondition($stackPtr, T_USE);
632
        } else {
633
            // PHPCS 1.x.
634
            $tokens         = $phpcsFile->getTokens();
635
            $maybeCurlyOpen = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
636
            if ($tokens[$maybeCurlyOpen]['code'] === T_OPEN_CURLY_BRACKET) {
637
                $maybeUseStatement = $phpcsFile->findPrevious($ignoreTokens, ($maybeCurlyOpen - 1), null, true);
638
                if ($tokens[$maybeUseStatement]['code'] === T_USE) {
639
                    return true;
640
                }
641
            }
642
            return false;
643
        }
644
    }
645
646
647
    /**
648
     * Returns the fully qualified class name for a new class instantiation.
649
     *
650
     * Returns an empty string if the class name could not be reliably inferred.
651
     *
652
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
653
     * @param int                   $stackPtr  The position of a T_NEW token.
654
     *
655
     * @return string
656
     */
657
    public function getFQClassNameFromNewToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
658
    {
659
        $tokens = $phpcsFile->getTokens();
660
661
        // Check for the existence of the token.
662
        if (isset($tokens[$stackPtr]) === false) {
663
            return '';
664
        }
665
666
        if ($tokens[$stackPtr]['code'] !== T_NEW) {
667
            return '';
668
        }
669
670
        $start = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
671
        if ($start === false) {
672
            return '';
673
        }
674
675
        // Bow out if the next token is a variable as we don't know where it was defined.
676
        if ($tokens[$start]['code'] === T_VARIABLE) {
677
            return '';
678
        }
679
680
        // Bow out if the next token is the class keyword.
681
        if ($tokens[$start]['type'] === 'T_ANON_CLASS' || $tokens[$start]['code'] === T_CLASS) {
682
            return '';
683
        }
684
685
        $find = array(
686
            T_NS_SEPARATOR,
687
            T_STRING,
688
            T_NAMESPACE,
689
            T_WHITESPACE,
690
        );
691
692
        $end       = $phpcsFile->findNext($find, ($start + 1), null, true, null, true);
693
        $className = $phpcsFile->getTokensAsString($start, ($end - $start));
694
        $className = trim($className);
695
696
        return $this->getFQName($phpcsFile, $stackPtr, $className);
697
    }
698
699
700
    /**
701
     * Returns the fully qualified name of the class that the specified class extends.
702
     *
703
     * Returns an empty string if the class does not extend another class or if
704
     * the class name could not be reliably inferred.
705
     *
706
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
707
     * @param int                   $stackPtr  The position of a T_CLASS token.
708
     *
709
     * @return string
710
     */
711
    public function getFQExtendedClassName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
712
    {
713
        $tokens = $phpcsFile->getTokens();
714
715
        // Check for the existence of the token.
716
        if (isset($tokens[$stackPtr]) === false) {
717
            return '';
718
        }
719
720 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_CLASS
721
            && $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
722
            && $tokens[$stackPtr]['type'] !== 'T_INTERFACE'
723
        ) {
724
            return '';
725
        }
726
727
        $extends = PHPCSHelper::findExtendedClassName($phpcsFile, $stackPtr);
728
        if (empty($extends) || is_string($extends) === false) {
729
            return '';
730
        }
731
732
        return $this->getFQName($phpcsFile, $stackPtr, $extends);
733
    }
734
735
736
    /**
737
     * Returns the class name for the static usage of a class.
738
     * This can be a call to a method, the use of a property or constant.
739
     *
740
     * Returns an empty string if the class name could not be reliably inferred.
741
     *
742
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
743
     * @param int                   $stackPtr  The position of a T_NEW token.
744
     *
745
     * @return string
746
     */
747
    public function getFQClassNameFromDoubleColonToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
748
    {
749
        $tokens = $phpcsFile->getTokens();
750
751
        // Check for the existence of the token.
752
        if (isset($tokens[$stackPtr]) === false) {
753
            return '';
754
        }
755
756
        if ($tokens[$stackPtr]['code'] !== T_DOUBLE_COLON) {
757
            return '';
758
        }
759
760
        // Nothing to do if previous token is a variable as we don't know where it was defined.
761
        if ($tokens[$stackPtr - 1]['code'] === T_VARIABLE) {
762
            return '';
763
        }
764
765
        // Nothing to do if 'parent' or 'static' as we don't know how far the class tree extends.
766
        if (in_array($tokens[$stackPtr - 1]['code'], array(T_PARENT, T_STATIC), true)) {
767
            return '';
768
        }
769
770
        // Get the classname from the class declaration if self is used.
771
        if ($tokens[$stackPtr - 1]['code'] === T_SELF) {
772
            $classDeclarationPtr = $phpcsFile->findPrevious(T_CLASS, $stackPtr - 1);
773
            if ($classDeclarationPtr === false) {
774
                return '';
775
            }
776
            $className = $phpcsFile->getDeclarationName($classDeclarationPtr);
777
            return $this->getFQName($phpcsFile, $classDeclarationPtr, $className);
778
        }
779
780
        $find = array(
781
            T_NS_SEPARATOR,
782
            T_STRING,
783
            T_NAMESPACE,
784
            T_WHITESPACE,
785
        );
786
787
        $start = $phpcsFile->findPrevious($find, $stackPtr - 1, null, true, null, true);
788
        if ($start === false || isset($tokens[($start + 1)]) === false) {
789
            return '';
790
        }
791
792
        $start     = ($start + 1);
793
        $className = $phpcsFile->getTokensAsString($start, ($stackPtr - $start));
794
        $className = trim($className);
795
796
        return $this->getFQName($phpcsFile, $stackPtr, $className);
797
    }
798
799
800
    /**
801
     * Get the Fully Qualified name for a class/function/constant etc.
802
     *
803
     * Checks if a class/function/constant name is already fully qualified and
804
     * if not, enrich it with the relevant namespace information.
805
     *
806
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
807
     * @param int                   $stackPtr  The position of the token.
808
     * @param string                $name      The class / function / constant name.
809
     *
810
     * @return string
811
     */
812
    public function getFQName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $name)
813
    {
814
        if (strpos($name, '\\') === 0) {
815
            // Already fully qualified.
816
            return $name;
817
        }
818
819
        // Remove the namespace keyword if used.
820
        if (strpos($name, 'namespace\\') === 0) {
821
            $name = substr($name, 10);
822
        }
823
824
        $namespace = $this->determineNamespace($phpcsFile, $stackPtr);
825
826
        if ($namespace === '') {
827
            return '\\' . $name;
828
        } else {
829
            return '\\' . $namespace . '\\' . $name;
830
        }
831
    }
832
833
834
    /**
835
     * Is the class/function/constant name namespaced or global ?
836
     *
837
     * @param string $FQName Fully Qualified name of a class, function etc.
838
     *                       I.e. should always start with a `\`.
839
     *
840
     * @return bool True if namespaced, false if global.
841
     */
842
    public function isNamespaced($FQName)
843
    {
844
        if (strpos($FQName, '\\') !== 0) {
845
            throw new \PHP_CodeSniffer_Exception('$FQName must be a fully qualified name');
846
        }
847
848
        return (strpos(substr($FQName, 1), '\\') !== false);
849
    }
850
851
852
    /**
853
     * Determine the namespace name an arbitrary token lives in.
854
     *
855
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
856
     * @param int                   $stackPtr  The token position for which to determine the namespace.
857
     *
858
     * @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
859
     */
860
    public function determineNamespace(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
861
    {
862
        $tokens = $phpcsFile->getTokens();
863
864
        // Check for the existence of the token.
865
        if (isset($tokens[$stackPtr]) === false) {
866
            return '';
867
        }
868
869
        // Check for scoped namespace {}.
870
        if (empty($tokens[$stackPtr]['conditions']) === false) {
871
            $namespacePtr = $phpcsFile->getCondition($stackPtr, T_NAMESPACE);
872
            if ($namespacePtr !== false) {
873
                $namespace = $this->getDeclaredNamespaceName($phpcsFile, $namespacePtr);
874
                if ($namespace !== false) {
875
                    return $namespace;
876
                }
877
878
                // We are in a scoped namespace, but couldn't determine the name. Searching for a global namespace is futile.
879
                return '';
880
            }
881
        }
882
883
        /*
884
         * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
885
         * Keeping in mind that:
886
         * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
887
         * - the namespace keyword can also be used as part of a function/method call and such.
888
         * - that a non-named namespace resolves to the global namespace.
889
         */
890
        $previousNSToken = $stackPtr;
891
        $namespace       = false;
892
        do {
893
            $previousNSToken = $phpcsFile->findPrevious(T_NAMESPACE, ($previousNSToken - 1));
894
895
            // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
896
            if (empty($tokens[$previousNSToken]['scope_condition']) === false && $tokens[$previousNSToken]['scope_condition'] === $previousNSToken) {
897
                break;
898
            }
899
900
            $namespace = $this->getDeclaredNamespaceName($phpcsFile, $previousNSToken);
901
902
        } while ($namespace === false && $previousNSToken !== false);
903
904
        // If we still haven't got a namespace, return an empty string.
905
        if ($namespace === false) {
906
            return '';
907
        } else {
908
            return $namespace;
909
        }
910
    }
911
912
    /**
913
     * Get the complete namespace name for a namespace declaration.
914
     *
915
     * For hierarchical namespaces, the name will be composed of several tokens,
916
     * i.e. MyProject\Sub\Level which will be returned together as one string.
917
     *
918
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
919
     * @param int|bool              $stackPtr  The position of a T_NAMESPACE token.
920
     *
921
     * @return string|false Namespace name or false if not a namespace declaration.
922
     *                      Namespace name can be an empty string for global namespace declaration.
923
     */
924
    public function getDeclaredNamespaceName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
925
    {
926
        $tokens = $phpcsFile->getTokens();
927
928
        // Check for the existence of the token.
929 View Code Duplication
        if ($stackPtr === false || isset($tokens[$stackPtr]) === false) {
930
            return false;
931
        }
932
933
        if ($tokens[$stackPtr]['code'] !== T_NAMESPACE) {
934
            return false;
935
        }
936
937
        if ($tokens[($stackPtr + 1)]['code'] === T_NS_SEPARATOR) {
938
            // Not a namespace declaration, but use of, i.e. namespace\someFunction();
939
            return false;
940
        }
941
942
        $nextToken = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
943
        if ($tokens[$nextToken]['code'] === T_OPEN_CURLY_BRACKET) {
944
            // Declaration for global namespace when using multiple namespaces in a file.
945
            // I.e.: namespace {}
946
            return '';
947
        }
948
949
        // Ok, this should be a namespace declaration, so get all the parts together.
950
        $validTokens = array(
951
            T_STRING       => true,
952
            T_NS_SEPARATOR => true,
953
            T_WHITESPACE   => true,
954
        );
955
956
        $namespaceName = '';
957
        while (isset($validTokens[$tokens[$nextToken]['code']]) === true) {
958
            $namespaceName .= trim($tokens[$nextToken]['content']);
959
            $nextToken++;
960
        }
961
962
        return $namespaceName;
963
    }
964
965
966
    /**
967
     * Get the stack pointer for a return type token for a given function.
968
     *
969
     * Compatible layer for older PHPCS versions which don't recognize
970
     * return type hints correctly.
971
     *
972
     * Expects to be passed T_RETURN_TYPE, T_FUNCTION or T_CLOSURE token.
973
     *
974
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
975
     * @param int                   $stackPtr  The position of the token.
976
     *
977
     * @return int|false Stack pointer to the return type token or false if
978
     *                   no return type was found or the passed token was
979
     *                   not of the correct type.
980
     */
981
    public function getReturnTypeHintToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
982
    {
983
        $tokens = $phpcsFile->getTokens();
984
985
        if (defined('T_RETURN_TYPE') && $tokens[$stackPtr]['code'] === T_RETURN_TYPE) {
986
            return $tokens[$stackPtr]['code'];
987
        }
988
989 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION && $tokens[$stackPtr]['code'] !== T_CLOSURE) {
990
            return false;
991
        }
992
993
        if (isset($tokens[$stackPtr]['parenthesis_closer'], $tokens[$stackPtr]['scope_opener']) === false
994
            || ($tokens[$stackPtr]['parenthesis_closer'] + 1) === $tokens[$stackPtr]['scope_opener']
995
        ) {
996
            return false;
997
        }
998
999
        $hasColon = $phpcsFile->findNext(
1000
            array(T_COLON, T_INLINE_ELSE),
1001
            ($tokens[$stackPtr]['parenthesis_closer'] + 1),
1002
            $tokens[$stackPtr]['scope_opener']
1003
        );
1004
        if ($hasColon === false) {
1005
            return false;
1006
        }
1007
1008
        // `self`, `parent` and `callable` are not being recognized as return types in PHPCS < 2.6.0.
1009
        $unrecognizedTypes = array(
1010
            T_CALLABLE,
1011
            T_SELF,
1012
            T_PARENT,
1013
        );
1014
1015
        // Return types are not recognized at all in PHPCS < 2.4.0.
1016
        if (defined('T_RETURN_TYPE') === false) {
1017
            $unrecognizedTypes[] = T_ARRAY;
1018
            $unrecognizedTypes[] = T_STRING;
1019
        }
1020
1021
        return $phpcsFile->findPrevious($unrecognizedTypes, ($tokens[$stackPtr]['scope_opener'] - 1), $hasColon);
1022
    }
1023
1024
1025
    /**
1026
     * Get the complete return type declaration for a given function.
1027
     *
1028
     * Cross-version compatible way to retrieve the complete return type declaration.
1029
     *
1030
     * For a classname-based return type, PHPCS, as well as the Sniff::getReturnTypeHintToken()
1031
     * method will mark the classname as the return type token.
1032
     * This method will find preceeding namespaces and namespace separators and will return a
1033
     * string containing the qualified return type declaration.
1034
     *
1035
     * Expects to be passed a T_RETURN_TYPE token or the return value from a call to
1036
     * the Sniff::getReturnTypeHintToken() method.
1037
     *
1038
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1039
     * @param int                   $stackPtr  The position of the return type token.
1040
     *
1041
     * @return string|false The name of the return type token.
1042
     */
1043
    public function getReturnTypeHintName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1044
    {
1045
        $tokens = $phpcsFile->getTokens();
1046
1047
        // In older PHPCS versions, the nullable indicator will turn a return type colon into a T_INLINE_ELSE.
1048
        $colon = $phpcsFile->findPrevious(array(T_COLON, T_INLINE_ELSE, T_FUNCTION, T_CLOSE_PARENTHESIS), ($stackPtr - 1));
1049
        if ($colon === false
1050
            || ($tokens[$colon]['code'] !== T_COLON && $tokens[$colon]['code'] !== T_INLINE_ELSE)
1051
        ) {
1052
            // Shouldn't happen, just in case.
1053
            return;
1054
        }
1055
1056
        $returnTypeHint = '';
1057
        for ($i = ($colon + 1); $i < $stackPtr; $i++) {
1058
            if ($tokens[$i]['code'] === T_STRING || $tokens[$i]['code'] === T_NS_SEPARATOR) {
1059
                $returnTypeHint .= $tokens[$i]['content'];
1060
            }
1061
        }
1062
        $returnTypeHint .= $tokens[$stackPtr]['content'];
1063
1064
        return $returnTypeHint;
1065
    }
1066
1067
1068
    /**
1069
     * Check whether a T_VARIABLE token is a class property declaration.
1070
     *
1071
     * Compatibility layer for PHPCS cross-version compatibility
1072
     * as PHPCS 2.4.0 - 2.7.1 does not have good enough support for
1073
     * anonymous classes. Along the same lines, the`getMemberProperties()`
1074
     * method does not support the `var` prefix.
1075
     *
1076
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1077
     * @param int                   $stackPtr  The position in the stack of the
1078
     *                                         T_VARIABLE token to verify.
1079
     *
1080
     * @return bool
1081
     */
1082
    public function isClassProperty(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1083
    {
1084
        $tokens = $phpcsFile->getTokens();
1085
1086 View Code Duplication
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_VARIABLE) {
1087
            return false;
1088
        }
1089
1090
        // Note: interfaces can not declare properties.
1091
        $validScopes = array(
1092
            'T_CLASS'      => true,
1093
            'T_ANON_CLASS' => true,
1094
            'T_TRAIT'      => true,
1095
        );
1096
        if ($this->validDirectScope($phpcsFile, $stackPtr, $validScopes) === true) {
1097
            // Make sure it's not a method parameter.
1098
            if (empty($tokens[$stackPtr]['nested_parenthesis']) === true) {
1099
                return true;
1100
            }
1101
        }
1102
1103
        return false;
1104
    }
1105
1106
1107
    /**
1108
     * Check whether a T_CONST token is a class constant declaration.
1109
     *
1110
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1111
     * @param int                   $stackPtr  The position in the stack of the
1112
     *                                         T_CONST token to verify.
1113
     *
1114
     * @return bool
1115
     */
1116
    public function isClassConstant(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1117
    {
1118
        $tokens = $phpcsFile->getTokens();
1119
1120 View Code Duplication
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== T_CONST) {
1121
            return false;
1122
        }
1123
1124
        // Note: traits can not declare constants.
1125
        $validScopes = array(
1126
            'T_CLASS'      => true,
1127
            'T_ANON_CLASS' => true,
1128
            'T_INTERFACE'  => true,
1129
        );
1130
        if ($this->validDirectScope($phpcsFile, $stackPtr, $validScopes) === true) {
1131
            return true;
1132
        }
1133
1134
        return false;
1135
    }
1136
1137
1138
    /**
1139
     * Check whether the direct wrapping scope of a token is within a limited set of
1140
     * acceptable tokens.
1141
     *
1142
     * Used to check, for instance, if a T_CONST is a class constant.
1143
     *
1144
     * @param \PHP_CodeSniffer_File $phpcsFile   Instance of phpcsFile.
1145
     * @param int                   $stackPtr    The position in the stack of the
1146
     *                                           T_CONST token to verify.
1147
     * @param array                 $validScopes Array of token types.
1148
     *                                           Keys should be the token types in string
1149
     *                                           format to allow for newer token types.
1150
     *                                           Value is irrelevant.
1151
     *
1152
     * @return bool
1153
     */
1154
    protected function validDirectScope(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $validScopes)
1155
    {
1156
        $tokens = $phpcsFile->getTokens();
1157
1158
        if (empty($tokens[$stackPtr]['conditions']) === true) {
1159
            return false;
1160
        }
1161
1162
        /*
1163
         * Check only the direct wrapping scope of the token.
1164
         */
1165
        $conditions = array_keys($tokens[$stackPtr]['conditions']);
1166
        $ptr        = array_pop($conditions);
1167
1168
        if (isset($tokens[$ptr]) === false) {
1169
            return false;
1170
        }
1171
1172
        if (isset($validScopes[$tokens[$ptr]['type']]) === true) {
1173
            return true;
1174
        }
1175
1176
        return false;
1177
    }
1178
1179
1180
    /**
1181
     * Get an array of just the type hints from a function declaration.
1182
     *
1183
     * Expects to be passed T_FUNCTION or T_CLOSURE token.
1184
     *
1185
     * Strips potential nullable indicator and potential global namespace
1186
     * indicator from the type hints before returning them.
1187
     *
1188
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1189
     * @param int                   $stackPtr  The position of the token.
1190
     *
1191
     * @return array Array with type hints or an empty array if
1192
     *               - the function does not have any parameters
1193
     *               - no type hints were found
1194
     *               - or the passed token was not of the correct type.
1195
     */
1196
    public function getTypeHintsFromFunctionDeclaration(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1197
    {
1198
        $tokens = $phpcsFile->getTokens();
1199
1200 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION && $tokens[$stackPtr]['code'] !== T_CLOSURE) {
1201
            return array();
1202
        }
1203
1204
        $parameters = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
1205
        if (empty($parameters) || is_array($parameters) === false) {
1206
            return array();
1207
        }
1208
1209
        $typeHints = array();
1210
1211
        foreach ($parameters as $param) {
1212
            if ($param['type_hint'] === '') {
1213
                continue;
1214
            }
1215
1216
            // Strip off potential nullable indication.
1217
            $typeHint = ltrim($param['type_hint'], '?');
1218
1219
            // Strip off potential (global) namespace indication.
1220
            $typeHint = ltrim($typeHint, '\\');
1221
1222
            if ($typeHint !== '') {
1223
                $typeHints[] = $typeHint;
1224
            }
1225
        }
1226
1227
        return $typeHints;
1228
    }
1229
1230
1231
    /**
1232
     * Get the hash algorithm name from the parameter in a hash function call.
1233
     *
1234
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1235
     * @param int                   $stackPtr  The position of the T_STRING function token.
1236
     *
1237
     * @return string|false The algorithm name without quotes if this was a relevant hash
1238
     *                      function call or false if it was not.
1239
     */
1240
    public function getHashAlgorithmParameter(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1241
    {
1242
        $tokens = $phpcsFile->getTokens();
1243
1244
        // Check for the existence of the token.
1245
        if (isset($tokens[$stackPtr]) === false) {
1246
            return false;
1247
        }
1248
1249
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
1250
            return false;
1251
        }
1252
1253
        $functionName   = $tokens[$stackPtr]['content'];
1254
        $functionNameLc = strtolower($functionName);
1255
1256
        // Bow out if not one of the functions we're targetting.
1257
        if (isset($this->hashAlgoFunctions[$functionNameLc]) === false) {
1258
            return false;
1259
        }
1260
1261
        // Get the parameter from the function call which should contain the algorithm name.
1262
        $algoParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->hashAlgoFunctions[$functionNameLc]);
1263
        if ($algoParam === false) {
1264
            return false;
1265
        }
1266
1267
        // Algorithm is a text string, so we need to remove the quotes.
1268
        $algo = strtolower(trim($algoParam['raw']));
1269
        $algo = $this->stripQuotes($algo);
1270
1271
        return $algo;
1272
    }
1273
1274
1275
    /**
1276
     * Determine whether an arbitrary T_STRING token is the use of a global constant.
1277
     *
1278
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
1279
     * @param int                   $stackPtr  The position of the function call token.
1280
     *
1281
     * @return bool
1282
     */
1283
    public function isUseOfGlobalConstant(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1284
    {
1285
        static $isLowPHPCS, $isLowPHP;
1286
1287
        $tokens = $phpcsFile->getTokens();
1288
1289
        // Check for the existence of the token.
1290
        if (isset($tokens[$stackPtr]) === false) {
1291
            return false;
1292
        }
1293
1294
        // Is this one of the tokens this function handles ?
1295
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
1296
            return false;
1297
        }
1298
1299
        // Check for older PHP, PHPCS version so we can compensate for misidentified tokens.
1300
        if (isset($isLowPHPCS, $isLowPHP) === false) {
1301
            $isLowPHP   = false;
1302
            $isLowPHPCS = false;
1303
            if (version_compare(PHP_VERSION_ID, '50400', '<')) {
1304
                $isLowPHP   = true;
1305
                $isLowPHPCS = version_compare(PHPCSHelper::getVersion(), '2.4.0', '<');
1306
            }
1307
        }
1308
1309
        $next = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
1310
        if ($next !== false
1311
            && ($tokens[$next]['code'] === T_OPEN_PARENTHESIS
1312
                || $tokens[$next]['code'] === T_DOUBLE_COLON)
1313
        ) {
1314
            // Function call or declaration.
1315
            return false;
1316
        }
1317
1318
        // Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a global constant.
1319
        $tokensToIgnore = array(
1320
            'T_NAMESPACE'       => true,
1321
            'T_USE'             => true,
1322
            'T_CLASS'           => true,
1323
            'T_TRAIT'           => true,
1324
            'T_INTERFACE'       => true,
1325
            'T_EXTENDS'         => true,
1326
            'T_IMPLEMENTS'      => true,
1327
            'T_NEW'             => true,
1328
            'T_FUNCTION'        => true,
1329
            'T_DOUBLE_COLON'    => true,
1330
            'T_OBJECT_OPERATOR' => true,
1331
            'T_INSTANCEOF'      => true,
1332
            'T_INSTEADOF'       => true,
1333
            'T_GOTO'            => true,
1334
            'T_AS'              => true,
1335
            'T_PUBLIC'          => true,
1336
            'T_PROTECTED'       => true,
1337
            'T_PRIVATE'         => true,
1338
        );
1339
1340
        $prev = $phpcsFile->findPrevious(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
1341
        if ($prev !== false
1342
            && (isset($tokensToIgnore[$tokens[$prev]['type']]) === true
1343
                || ($tokens[$prev]['code'] === T_STRING
1344
                    && (($isLowPHPCS === true
1345
                        && $tokens[$prev]['content'] === 'trait')
1346
                    || ($isLowPHP === true
1347
                        && $tokens[$prev]['content'] === 'insteadof'))))
1348
        ) {
1349
            // Not the use of a constant.
1350
            return false;
1351
        }
1352
1353
        if ($prev !== false
1354
            && $tokens[$prev]['code'] === T_NS_SEPARATOR
1355
            && $tokens[($prev - 1)]['code'] === T_STRING
1356
        ) {
1357
            // Namespaced constant of the same name.
1358
            return false;
1359
        }
1360
1361
        if ($prev !== false
1362
            && $tokens[$prev]['code'] === T_CONST
1363
            && $this->isClassConstant($phpcsFile, $prev) === true
1364
        ) {
1365
            // Class constant declaration of the same name.
1366
            return false;
1367
        }
1368
1369
        /*
1370
         * Deal with a number of variations of use statements.
1371
         */
1372
        for ($i = $stackPtr; $i > 0; $i--) {
1373
            if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
1374
                break;
1375
            }
1376
        }
1377
1378
        $firstOnLine = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true);
1379
        if ($firstOnLine !== false && $tokens[$firstOnLine]['code'] === T_USE) {
1380
            $nextOnLine = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($firstOnLine + 1), null, true);
1381
            if ($nextOnLine !== false) {
1382
                if (($tokens[$nextOnLine]['code'] === T_STRING && $tokens[$nextOnLine]['content'] === 'const')
1383
                    || $tokens[$nextOnLine]['code'] === T_CONST // Happens in some PHPCS versions.
1384
                ) {
1385
                    $hasNsSep = $phpcsFile->findNext(T_NS_SEPARATOR, ($nextOnLine + 1), $stackPtr);
1386
                    if ($hasNsSep !== false) {
1387
                        // Namespaced const (group) use statement.
1388
                        return false;
1389
                    }
1390
                } else {
1391
                    // Not a const use statement.
1392
                    return false;
1393
                }
1394
            }
1395
        }
1396
1397
        return true;
1398
    }
1399
1400
1401
    /**
1402
     * Determine whether the tokens between $start and $end together form a positive number
1403
     * as recognized by PHP.
1404
     *
1405
     * The outcome of this function is reliable for `true`, `false` should be regarded as
1406
     * "undetermined".
1407
     *
1408
     * Note: Zero is *not* regarded as a positive number.
1409
     *
1410
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1411
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1412
     *                                           token will be examined as part of the snippet.
1413
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1414
     *                                           token will be examined as part of the snippet.
1415
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1416
     *
1417
     * @return bool True if PHP would evaluate the snippet as a positive number.
1418
     *              False if not or if it could not be reliably determined
1419
     *              (variable or calculations and such).
1420
     */
1421 View Code Duplication
    public function isPositiveNumber(\PHP_CodeSniffer_File $phpcsFile, $start, $end, $allowFloats = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1422
    {
1423
        $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
1424
1425
        if ($number === false) {
1426
            return false;
1427
        }
1428
1429
        return ($number > 0);
1430
    }
1431
1432
1433
    /**
1434
     * Determine whether the tokens between $start and $end together form a negative number
1435
     * as recognized by PHP.
1436
     *
1437
     * The outcome of this function is reliable for `true`, `false` should be regarded as
1438
     * "undetermined".
1439
     *
1440
     * Note: Zero is *not* regarded as a negative number.
1441
     *
1442
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1443
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1444
     *                                           token will be examined as part of the snippet.
1445
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1446
     *                                           token will be examined as part of the snippet.
1447
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1448
     *
1449
     * @return bool True if PHP would evaluate the snippet as a negative number.
1450
     *              False if not or if it could not be reliably determined
1451
     *              (variable or calculations and such).
1452
     */
1453 View Code Duplication
    public function isNegativeNumber(\PHP_CodeSniffer_File $phpcsFile, $start, $end, $allowFloats = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1454
    {
1455
        $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
1456
1457
        if ($number === false) {
1458
            return false;
1459
        }
1460
1461
        return ($number < 0);
1462
1463
    }
1464
1465
    /**
1466
     * Determine whether the tokens between $start and $end together form a number
1467
     * as recognized by PHP.
1468
     *
1469
     * The outcome of this function is reliable for "true-ish" values, `false` should
1470
     * be regarded as "undetermined".
1471
     *
1472
     * @link https://3v4l.org/npTeM
1473
     *
1474
     * Mainly intended for examining variable assignments, function call parameters, array values
1475
     * where the start and end of the snippet to examine is very clear.
1476
     *
1477
     * @param \PHP_CodeSniffer_File $phpcsFile   The file being scanned.
1478
     * @param int                   $start       Start of the snippet (inclusive), i.e. this
1479
     *                                           token will be examined as part of the snippet.
1480
     * @param int                   $end         End of the snippet (inclusive), i.e. this
1481
     *                                           token will be examined as part of the snippet.
1482
     * @param bool                  $allowFloats Whether to only consider integers, or also floats.
1483
     *
1484
     * @return int|float|bool The number found if PHP would evaluate the snippet as a number.
1485
     *                        The return type will be int if $allowFloats is false, if
1486
     *                        $allowFloats is true, the return type will be float.
1487
     *                        False will be returned when the snippet does not evaluate to a
1488
     *                        number or if it could not be reliably determined
1489
     *                        (variable or calculations and such).
1490
     */
1491
    protected function isNumber(\PHP_CodeSniffer_File $phpcsFile, $start, $end, $allowFloats = false)
1492
    {
1493
        $stringTokens  = array_flip(\PHP_CodeSniffer_Tokens::$heredocTokens); // Flipping for PHPCS 1.x compat.
1494
        $stringTokens += array_flip(\PHP_CodeSniffer_Tokens::$stringTokens); // Flipping for PHPCS 1.x compat.
1495
1496
        $validTokens            = array();
1497
        $validTokens[T_LNUMBER] = true;
1498
        $validTokens[T_TRUE]    = true; // Evaluates to int 1.
1499
        $validTokens[T_FALSE]   = true; // Evaluates to int 0.
1500
1501
        if ($allowFloats === true) {
1502
            $validTokens[T_DNUMBER] = true;
1503
        }
1504
1505
        $maybeValidTokens = $stringTokens + $validTokens;
1506
1507
        $tokens         = $phpcsFile->getTokens();
1508
        $searchEnd      = ($end + 1);
1509
        $negativeNumber = false;
1510
1511
        if (isset($tokens[$start], $tokens[$searchEnd]) === false) {
1512
            return false;
1513
        }
1514
1515
        $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, $start, $searchEnd, true);
1516
        if ($nextNonEmpty !== false
1517
            && ($tokens[$nextNonEmpty]['code'] === T_PLUS
1518
            || $tokens[$nextNonEmpty]['code'] === T_MINUS)
1519
        ) {
1520
1521
            if ($tokens[$nextNonEmpty]['code'] === T_MINUS) {
1522
                $negativeNumber = true;
1523
            }
1524
1525
            $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
1526
        }
1527
1528
        if ($nextNonEmpty === false || isset($maybeValidTokens[$tokens[$nextNonEmpty]['code']]) === false) {
1529
            return false;
1530
        }
1531
1532
        $content = false;
1533
        if ($tokens[$nextNonEmpty]['code'] === T_LNUMBER
1534
            || $tokens[$nextNonEmpty]['code'] === T_DNUMBER
1535
        ) {
1536
            $content = (float) $tokens[$nextNonEmpty]['content'];
1537
        } elseif ($tokens[$nextNonEmpty]['code'] === T_TRUE) {
1538
            $content = 1.0;
1539
        } elseif ($tokens[$nextNonEmpty]['code'] === T_FALSE) {
1540
            $content = 0.0;
1541
        } elseif (isset($stringTokens[$tokens[$nextNonEmpty]['code']]) === true) {
1542
1543
            if ($tokens[$nextNonEmpty]['code'] === T_START_HEREDOC
1544
                || $tokens[$nextNonEmpty]['code'] === T_START_NOWDOC
1545
            ) {
1546
                // Skip past heredoc/nowdoc opener to the first content.
1547
                $firstDocToken = $phpcsFile->findNext(array(T_HEREDOC, T_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
1548
                if ($firstDocToken === false) {
1549
                    // Live coding or parse error.
1550
                    return false;
1551
                }
1552
1553
                $stringContent = $content = $tokens[$firstDocToken]['content'];
1554
1555
                // Skip forward to the end in preparation for the next part of the examination.
1556
                $nextNonEmpty = $phpcsFile->findNext(array(T_END_HEREDOC, T_END_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
1557
                if ($nextNonEmpty === false) {
1558
                    // Live coding or parse error.
1559
                    return false;
1560
                }
1561
            } else {
1562
                // Gather subsequent lines for a multi-line string.
1563
                for ($i = $nextNonEmpty; $i < $searchEnd; $i++) {
1564
                    if ($tokens[$i]['code'] !== $tokens[$nextNonEmpty]['code']) {
1565
                        break;
1566
                    }
1567
                    $content .= $tokens[$i]['content'];
1568
                }
1569
1570
                $nextNonEmpty  = --$i;
1571
                $content       = $this->stripQuotes($content);
1572
                $stringContent = $content;
1573
            }
1574
1575
            /*
1576
             * Regexes based on the formats outlined in the manual, created by JRF.
1577
             * @link http://php.net/manual/en/language.types.float.php
1578
             */
1579
            $regexInt   = '`^\s*[0-9]+`';
1580
            $regexFloat = '`^\s*(?:[+-]?(?:(?:(?P<LNUM>[0-9]+)|(?P<DNUM>([0-9]*\.(?P>LNUM)|(?P>LNUM)\.[0-9]*)))[eE][+-]?(?P>LNUM))|(?P>DNUM))`';
1581
1582
            $intString   = preg_match($regexInt, $content, $intMatch);
1583
            $floatString = preg_match($regexFloat, $content, $floatMatch);
1584
1585
            // Does the text string start with a number ? If so, PHP would juggle it and use it as a number.
1586
            if ($allowFloats === false) {
1587
                if ($intString !== 1 || $floatString === 1) {
1588
                    // Found non-numeric start or float. Only integers targetted.
1589
                    return false;
1590
                }
1591
1592
                $content = (float) trim($intMatch[0]);
1593
            } else {
1594
                if ($intString !== 1 && $floatString !== 1) {
1595
                    return false;
1596
                }
1597
1598
                $content = ($floatString === 1) ? (float) trim($floatMatch[0]) : (float) trim($intMatch[0]);
1599
            }
1600
1601
            // Allow for different behaviour for hex numeric strings between PHP 5 vs PHP 7.
1602
            if ($intString === 1 && trim($intMatch[0]) === '0'
1603
                && preg_match('`^\s*(0x[A-Fa-f0-9]+)`', $stringContent, $hexNumberString) === 1
1604
                && $this->supportsBelow('5.6') === true
1605
            ) {
1606
                // The filter extension still allows for hex numeric strings in PHP 7, so
1607
                // use that to get the numeric value if possible.
1608
                // If the filter extension is not available, the value will be zero, but so be it.
1609
                if (function_exists('filter_var')) {
1610
                    $filtered = filter_var($hexNumberString[1], FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX);
1611
                    if ($filtered !== false) {
1612
                        $content = $filtered;
1613
                    }
1614
                }
1615
            }
1616
        }
1617
1618
        // OK, so we have a number, now is there still more code after it ?
1619
        $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
1620
        if ($nextNonEmpty !== false) {
1621
            return false;
1622
        }
1623
1624
        if ($negativeNumber === true) {
1625
            $content = -$content;
1626
        }
1627
1628
        if ($allowFloats === false) {
1629
            return (int) $content;
1630
        }
1631
1632
        return $content;
1633
    }
1634
1635
}//end class
1636