Completed
Push — master ( e1af2c...160e8a )
by Wim
9s
created

getFQClassNameFromNewToken()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 18
nc 4
nop 2
1
<?php
2
/**
3
 * PHPCompatibility_Sniff.
4
 *
5
 * PHP version 5.6
6
 *
7
 * @category  PHP
8
 * @package   PHPCompatibility
9
 * @author    Wim Godden <[email protected]>
10
 * @copyright 2014 Cu.be Solutions bvba
11
 */
12
13
/**
14
 * PHPCompatibility_Sniff.
15
 *
16
 * @category  PHP
17
 * @package   PHPCompatibility
18
 * @author    Wim Godden <[email protected]>
19
 * @version   1.1.0
20
 * @copyright 2014 Cu.be Solutions bvba
21
 */
22
abstract class PHPCompatibility_Sniff implements PHP_CodeSniffer_Sniff
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
23
{
24
25
    const REGEX_COMPLEX_VARS = '`(?:(\{)?(?<!\\\\)\$)?(\{)?(?<!\\\\)\$(\{)?(?P<varname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:->\$?(?P>varname)|\[[^\]]+\]|::\$?(?P>varname)|\([^\)]*\))*(?(3)\}|)(?(2)\}|)(?(1)\}|)`';
26
27
    /**
28
     * List of functions using hash algorithm as parameter (always the first parameter).
29
     *
30
     * Used by the new/removed hash algorithm sniffs.
31
     * Key is the function name, value is the 1-based parameter position in the function call.
32
     *
33
     * @var array
34
     */
35
    protected $hashAlgoFunctions = array(
36
        'hash_file'      => 1,
37
        'hash_hmac_file' => 1,
38
        'hash_hmac'      => 1,
39
        'hash_init'      => 1,
40
        'hash_pbkdf2'    => 1,
41
        'hash'           => 1,
42
    );
43
44
45
    /**
46
     * List of functions which take an ini directive as parameter (always the first parameter).
47
     *
48
     * Used by the new/removed ini directives sniffs.
49
     * Key is the function name, value is the 1-based parameter position in the function call.
50
     *
51
     * @var array
52
     */
53
    protected $iniFunctions = array(
54
        'ini_get' => 1,
55
        'ini_set' => 1,
56
    );
57
58
59
/* The testVersion configuration variable may be in any of the following formats:
60
 * 1) Omitted/empty, in which case no version is specified.  This effectively
61
 *    disables all the checks provided by this standard.
62
 * 2) A single PHP version number, e.g. "5.4" in which case the standard checks that
63
 *    the code will run on that version of PHP (no deprecated features or newer
64
 *    features being used).
65
 * 3) A range, e.g. "5.0-5.5", in which case the standard checks the code will run
66
 *    on all PHP versions in that range, and that it doesn't use any features that
67
 *    were deprecated by the final version in the list, or which were not available
68
 *    for the first version in the list.
69
 * PHP version numbers should always be in Major.Minor format.  Both "5", "5.3.2"
70
 * would be treated as invalid, and ignored.
71
 * This standard doesn't support checking against PHP4, so the minimum version that
72
 * is recognised is "5.0".
73
 */
74
75
    private function getTestVersion()
76
    {
77
        /**
78
         * var $arrTestVersions will hold an array containing min/max version of PHP
79
         *   that we are checking against (see above).  If only a single version
80
         *   number is specified, then this is used as both the min and max.
81
         */
82
        static $arrTestVersions = array();
83
84
        $testVersion = trim(PHP_CodeSniffer::getConfigData('testVersion'));
85
86
        if (!isset($arrTestVersions[$testVersion]) && !empty($testVersion)) {
87
88
            $arrTestVersions[$testVersion] = array(null, null);
89
            if (preg_match('/^\d+\.\d+$/', $testVersion)) {
90
                $arrTestVersions[$testVersion] = array($testVersion, $testVersion);
91
            }
92
            elseif (preg_match('/^(\d+\.\d+)\s*-\s*(\d+\.\d+)$/', $testVersion,
93
                               $matches))
94
            {
95
                if (version_compare($matches[1], $matches[2], '>')) {
96
                    trigger_error("Invalid range in testVersion setting: '"
97
                                  . $testVersion . "'", E_USER_WARNING);
98
                }
99
                else {
100
                    $arrTestVersions[$testVersion] = array($matches[1], $matches[2]);
101
                }
102
            }
103
            elseif (!$testVersion == '') {
104
                trigger_error("Invalid testVersion setting: '" . $testVersion
105
                              . "'", E_USER_WARNING);
106
            }
107
        }
108
109
        if (isset($arrTestVersions[$testVersion])) {
110
            return $arrTestVersions[$testVersion];
111
        }
112
        else {
113
            return array(null, null);
114
        }
115
    }
116
117 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...
118
    {
119
        $testVersion = $this->getTestVersion();
120
        $testVersion = $testVersion[1];
121
122
        if (is_null($testVersion)
123
            || version_compare($testVersion, $phpVersion) >= 0
124
        ) {
125
            return true;
126
        } else {
127
            return false;
128
        }
129
    }//end supportsAbove()
130
131 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...
132
    {
133
        $testVersion = $this->getTestVersion();
134
        $testVersion = $testVersion[0];
135
136
        if (!is_null($testVersion)
137
            && version_compare($testVersion, $phpVersion) <= 0
138
        ) {
139
            return true;
140
        } else {
141
            return false;
142
        }
143
    }//end supportsBelow()
144
145
146
    /**
147
     * Add a PHPCS message to the output stack as either a warning or an error.
148
     *
149
     * @param PHP_CodeSniffer_File $phpcsFile The file the message applies to.
150
     * @param string               $message   The message.
151
     * @param int                  $stackPtr  The position of the token
152
     *                                        the message relates to.
153
     * @param bool                 $isError   Whether to report the message as an
154
     *                                        'error' or 'warning'.
155
     *                                        Defaults to true (error).
156
     * @param string               $code      The error code for the message.
157
     *                                        Defaults to 'Found'.
158
     * @param array                $data      Optional input for the data replacements.
159
     *
160
     * @return void
161
     */
162
    public function addMessage($phpcsFile, $message, $stackPtr, $isError, $code = 'Found', $data = array())
163
    {
164
        if ($isError === true) {
165
            $phpcsFile->addError($message, $stackPtr, $code, $data);
166
        } else {
167
            $phpcsFile->addWarning($message, $stackPtr, $code, $data);
168
        }
169
    }
170
171
172
    /**
173
     * Convert an arbitrary string to an alphanumeric string with underscores.
174
     *
175
     * Pre-empt issues with arbitrary strings being used as error codes in XML and PHP.
176
     *
177
     * @param string $baseString Arbitrary string.
178
     *
179
     * @return string
180
     */
181
    public function stringToErrorCode($baseString)
182
    {
183
        return preg_replace('`[^a-z0-9_]`i', '_', strtolower($baseString));
184
    }
185
186
187
    /**
188
     * Strip quotes surrounding an arbitrary string.
189
     *
190
     * Intended for use with the content of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
191
     *
192
     * @param string $string The raw string.
193
     *
194
     * @return string String without quotes around it.
195
     */
196
    public function stripQuotes($string) {
197
        return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
198
    }
199
200
201
    /**
202
     * Strip variables from an arbitrary double quoted string.
203
     *
204
     * Intended for use with the content of a T_DOUBLE_QUOTED_STRING.
205
     *
206
     * @param string $string The raw string.
207
     *
208
     * @return string String without variables in it.
209
     */
210
    public function stripVariables($string) {
211
        if (strpos($string, '$') === false) {
212
            return $string;
213
        }
214
215
        return preg_replace( self::REGEX_COMPLEX_VARS, '', $string );
216
    }
217
218
219
    /**
220
     * Make all top level array keys in an array lowercase.
221
     *
222
     * @param array $array Initial array.
223
     *
224
     * @return array Same array, but with all lowercase top level keys.
225
     */
226
    public function arrayKeysToLowercase($array)
227
    {
228
        $keys = array_keys($array);
229
        $keys = array_map('strtolower', $keys);
230
        return array_combine($keys, $array);
231
    }
232
233
234
    /**
235
     * Returns the name(s) of the interface(s) that the specified class implements.
236
     *
237
     * Returns FALSE on error or if there are no implemented interface names.
238
     *
239
     * {@internal Duplicate of same method as introduced in PHPCS 2.7.
240
     * Once the minimum supported PHPCS version for this sniff library goes beyond
241
     * that, this method can be removed and call to it replaced with
242
     * `$phpcsFile->findImplementedInterfaceNames($stackPtr)` calls.}}
243
     *
244
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
245
     * @param int                  $stackPtr  The position of the class token.
246
     *
247
     * @return array|false
248
     */
249
    public function findImplementedInterfaceNames(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
250
    {
251
        if (method_exists($phpcsFile, 'findImplementedInterfaceNames')) {
252
            return $phpcsFile->findImplementedInterfaceNames($stackPtr);
253
        }
254
255
        $tokens = $phpcsFile->getTokens();
256
257
        // Check for the existence of the token.
258
        if (isset($tokens[$stackPtr]) === false) {
259
            return false;
260
        }
261
262
        if ($tokens[$stackPtr]['code'] !== T_CLASS) {
263
            return false;
264
        }
265
266
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
267
            return false;
268
        }
269
270
        $classOpenerIndex = $tokens[$stackPtr]['scope_opener'];
271
        $implementsIndex  = $phpcsFile->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
272
        if ($implementsIndex === false) {
273
            return false;
274
        }
275
276
        $find = array(
277
                 T_NS_SEPARATOR,
278
                 T_STRING,
279
                 T_WHITESPACE,
280
                 T_COMMA,
281
                );
282
283
        $end  = $phpcsFile->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
284
        $name = $phpcsFile->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
285
        $name = trim($name);
286
287
        if ($name === '') {
288
            return false;
289
        } else {
290
            $names = explode(',', $name);
291
            $names = array_map('trim', $names);
292
            return $names;
293
        }
294
295
    }//end findImplementedInterfaceNames()
296
297
298
    /**
299
     * Checks if a function call has parameters.
300
     *
301
     * Expects to be passed the T_STRING stack pointer for the function call.
302
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
303
     *
304
     * @link https://github.com/wimg/PHPCompatibility/issues/120
305
     * @link https://github.com/wimg/PHPCompatibility/issues/152
306
     *
307
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
308
     * @param int                  $stackPtr  The position of the function call token.
309
     *
310
     * @return bool
311
     */
312
    public function doesFunctionCallHaveParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
313
    {
314
        $tokens = $phpcsFile->getTokens();
315
316
        // Check for the existence of the token.
317
        if (isset($tokens[$stackPtr]) === false) {
318
            return false;
319
        }
320
321
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
322
            return false;
323
        }
324
325
        // Next non-empty token should be the open parenthesis.
326
        $openParenthesis = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
327
        if ($openParenthesis === false || $tokens[$openParenthesis]['code'] !== T_OPEN_PARENTHESIS) {
328
            return false;
329
        }
330
331
        if (isset($tokens[$openParenthesis]['parenthesis_closer']) === false) {
332
            return false;
333
        }
334
335
        $closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
336
        $nextNonEmpty     = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $openParenthesis + 1, $closeParenthesis + 1, true);
337
338
        if ($nextNonEmpty === $closeParenthesis) {
339
            // No parameters.
340
            return false;
341
        }
342
343
        return true;
344
    }
345
346
347
    /**
348
     * Count the number of parameters a function call has been passed.
349
     *
350
     * Expects to be passed the T_STRING stack pointer for the function call.
351
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
352
     *
353
     * @link https://github.com/wimg/PHPCompatibility/issues/111
354
     * @link https://github.com/wimg/PHPCompatibility/issues/114
355
     * @link https://github.com/wimg/PHPCompatibility/issues/151
356
     *
357
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
358
     * @param int                  $stackPtr  The position of the function call token.
359
     *
360
     * @return int
361
     */
362
    public function getFunctionCallParameterCount(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
363
    {
364
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
365
            return 0;
366
        }
367
368
        return count($this->getFunctionCallParameters($phpcsFile, $stackPtr));
369
    }
370
371
372
    /**
373
     * Get information on all parameters passed to a function call.
374
     *
375
     * Expects to be passed the T_STRING stack pointer for the function call.
376
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
377
     *
378
     * Will return an multi-dimentional array with the start token pointer, end token
379
     * pointer and raw parameter value for all parameters. Index will be 1-based.
380
     * If no parameters are found, will return an empty array.
381
     *
382
     * @param PHP_CodeSniffer_File $phpcsFile     The file being scanned.
383
     * @param int                  $stackPtr      The position of the function call token.
384
     *
385
     * @return array
386
     */
387
    public function getFunctionCallParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
388
    {
389
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
390
            return array();
391
        }
392
393
        // Ok, we know we have a T_STRING with parameters and valid open & close parenthesis.
394
        $tokens = $phpcsFile->getTokens();
395
396
        $openParenthesis  = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
397
        $closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
398
399
        // Which nesting level is the one we are interested in ?
400
        $nestedParenthesisCount = 1;
401 View Code Duplication
        if (isset($tokens[$openParenthesis]['nested_parenthesis'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
402
            $nestedParenthesisCount = count($tokens[$openParenthesis]['nested_parenthesis']) + 1;
403
        }
404
405
        $parameters = array();
406
        $nextComma  = $openParenthesis;
407
        $paramStart = $openParenthesis + 1;
408
        $cnt        = 1;
409
        while ($nextComma = $phpcsFile->findNext(array(T_COMMA, T_CLOSE_PARENTHESIS, T_OPEN_SHORT_ARRAY), $nextComma + 1, $closeParenthesis + 1)) {
410
            // Ignore anything within short array definition brackets.
411
            if (
412
                $tokens[$nextComma]['type'] === 'T_OPEN_SHORT_ARRAY'
413
                &&
414
                ( isset($tokens[$nextComma]['bracket_opener']) && $tokens[$nextComma]['bracket_opener'] === $nextComma )
415
                &&
416
                isset($tokens[$nextComma]['bracket_closer'])
417
            ) {
418
                // Skip forward to the end of the short array definition.
419
                $nextComma = $tokens[$nextComma]['bracket_closer'];
420
                continue;
421
            }
422
423
            // Ignore comma's at a lower nesting level.
424
            if (
425
                $tokens[$nextComma]['type'] === 'T_COMMA'
426
                &&
427
                isset($tokens[$nextComma]['nested_parenthesis'])
428
                &&
429
                count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount
430
            ) {
431
                continue;
432
            }
433
434
            // Ignore closing parenthesis if not 'ours'.
435
            if ($tokens[$nextComma]['type'] === 'T_CLOSE_PARENTHESIS' && $nextComma !== $closeParenthesis) {
436
                continue;
437
            }
438
439
            // Ok, we've reached the end of the parameter.
440
            $parameters[$cnt]['start'] = $paramStart;
441
            $parameters[$cnt]['end']   = $nextComma - 1;
442
            $parameters[$cnt]['raw']   = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart)));
443
444
            // Check if there are more tokens before the closing parenthesis.
445
            // Prevents code like the following from setting a third parameter:
446
            // 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...
447
            $hasNextParam = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $nextComma + 1, $closeParenthesis, true, null, true);
448
            if ($hasNextParam === false) {
449
                break;
450
            }
451
452
            // Prepare for the next parameter.
453
            $paramStart = $nextComma + 1;
454
            $cnt++;
455
        }
456
457
        return $parameters;
458
    }
459
460
461
    /**
462
     * Get information on a specific parameter passed to a function call.
463
     *
464
     * Expects to be passed the T_STRING stack pointer for the function call.
465
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
466
     *
467
     * Will return a array with the start token pointer, end token pointer and the raw value
468
     * of the parameter at a specific offset.
469
     * If the specified parameter is not found, will return false.
470
     *
471
     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
472
     * @param int                  $stackPtr    The position of the function call token.
473
     * @param int                  $paramOffset The 1-based index position of the parameter to retrieve.
474
     *
475
     * @return array|false
476
     */
477
    public function getFunctionCallParameter(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $paramOffset)
478
    {
479
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
480
481
        if (isset($parameters[$paramOffset]) === false) {
482
            return false;
483
        }
484
        else {
485
            return $parameters[$paramOffset];
486
        }
487
    }
488
489
490
    /**
491
     * Verify whether a token is within a scoped condition.
492
     *
493
     * If the optional $validScopes parameter has been passed, the function
494
     * will check that the token has at least one condition which is of a
495
     * type defined in $validScopes.
496
     *
497
     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
498
     * @param int                  $stackPtr    The position of the token.
499
     * @param array|int            $validScopes Optional. Array of valid scopes
500
     *                                          or int value of a valid scope.
501
     *                                          Pass the T_.. constant(s) for the
502
     *                                          desired scope to this parameter.
503
     *
504
     * @return bool Without the optional $scopeTypes: True if within a scope, false otherwise.
505
     *              If the $scopeTypes are set: True if *one* of the conditions is a
506
     *              valid scope, false otherwise.
507
     */
508
    public function tokenHasScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $validScopes = null)
509
    {
510
        $tokens = $phpcsFile->getTokens();
511
512
        // Check for the existence of the token.
513
        if (isset($tokens[$stackPtr]) === false) {
514
            return false;
515
        }
516
517
        // No conditions = no scope.
518
        if (empty($tokens[$stackPtr]['conditions'])) {
519
            return false;
520
        }
521
522
        // Ok, there are conditions, do we have to check for specific ones ?
523
        if (isset($validScopes) === false) {
524
            return true;
525
        }
526
527
        if (is_int($validScopes)) {
528
            // Received an integer, so cast to array.
529
            $validScopes = (array) $validScopes;
530
        }
531
532
        if (empty($validScopes) || is_array($validScopes) === false) {
533
            // No valid scope types received, so will not comply.
534
            return false;
535
        }
536
537
        // Check for required scope types.
538
        foreach ($tokens[$stackPtr]['conditions'] as $pointer => $tokenCode) {
539
            if (in_array($tokenCode, $validScopes, true)) {
540
                return true;
541
            }
542
        }
543
544
        return false;
545
    }
546
547
548
    /**
549
     * Verify whether a token is within a class scope.
550
     *
551
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
552
     * @param int                  $stackPtr  The position of the token.
553
     * @param bool                 $strict    Whether to strictly check for the T_CLASS
554
     *                                        scope or also accept interfaces and traits
555
     *                                        as scope.
556
     *
557
     * @return bool True if within class scope, false otherwise.
558
     */
559
    public function inClassScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $strict = true)
560
    {
561
        $validScopes = array(T_CLASS);
562
        if ($strict === false) {
563
            $validScopes[] = T_INTERFACE;
564
            $validScopes[] = T_TRAIT;
565
        }
566
567
        return $this->tokenHasScope($phpcsFile, $stackPtr, $validScopes);
568
    }
569
570
571
    /**
572
     * Verify whether a token is within a scoped use statement.
573
     *
574
     * PHPCS cross-version compatibility method.
575
     *
576
     * In PHPCS 1.x no conditions are set for a scoped use statement.
577
     * This method works around that limitation.
578
     *
579
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
580
     * @param int                  $stackPtr  The position of the token.
581
     *
582
     * @return bool True if within use scope, false otherwise.
583
     */
584
    public function inUseScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
585
    {
586
        static $isLowPHPCS, $ignoreTokens;
587
588
        if (isset($isLowPHPCS) === false) {
589
            $isLowPHPCS = version_compare(PHP_CodeSniffer::VERSION, '2.0', '<');
590
        }
591
        if (isset($ignoreTokens) === false) {
592
            $ignoreTokens              = PHP_CodeSniffer_Tokens::$emptyTokens;
593
            $ignoreTokens[T_STRING]    = T_STRING;
594
            $ignoreTokens[T_AS]        = T_AS;
595
            $ignoreTokens[T_PUBLIC]    = T_PUBLIC;
596
            $ignoreTokens[T_PROTECTED] = T_PROTECTED;
597
            $ignoreTokens[T_PRIVATE]   = T_PRIVATE;
598
        }
599
600
        // PHPCS 2.0.
601
        if ($isLowPHPCS === false) {
602
            return $this->tokenHasScope($phpcsFile, $stackPtr, T_USE);
603
        } else {
604
            // PHPCS 1.x.
605
            $tokens         = $phpcsFile->getTokens();
606
            $maybeCurlyOpen = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
607
            if ($tokens[$maybeCurlyOpen]['code'] === T_OPEN_CURLY_BRACKET) {
608
                $maybeUseStatement = $phpcsFile->findPrevious($ignoreTokens, ($maybeCurlyOpen - 1), null, true);
609
                if ($tokens[$maybeUseStatement]['code'] === T_USE) {
610
                    return true;
611
                }
612
            }
613
            return false;
614
        }
615
    }
616
617
618
    /**
619
     * Returns the fully qualified class name for a new class instantiation.
620
     *
621
     * Returns an empty string if the class name could not be reliably inferred.
622
     *
623
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
624
     * @param int                  $stackPtr  The position of a T_NEW token.
625
     *
626
     * @return string
627
     */
628
    public function getFQClassNameFromNewToken(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
629
    {
630
        $tokens = $phpcsFile->getTokens();
631
632
        // Check for the existence of the token.
633
        if (isset($tokens[$stackPtr]) === false) {
634
            return '';
635
        }
636
637
        if ($tokens[$stackPtr]['code'] !== T_NEW) {
638
            return '';
639
        }
640
641
        $find = array(
642
                 T_NS_SEPARATOR,
643
                 T_STRING,
644
                 T_NAMESPACE,
645
                 T_WHITESPACE,
646
                );
647
648
        $start = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
649
        // Bow out if the next token is a variable as we don't know where it was defined.
650
        if ($tokens[$start]['code'] === T_VARIABLE) {
651
            return '';
652
        }
653
654
        $end       = $phpcsFile->findNext($find, ($start + 1), null, true, null, true);
655
        $className = $phpcsFile->getTokensAsString($start, ($end - $start));
656
        $className = trim($className);
657
658
        return $this->getFQName($phpcsFile, $stackPtr, $className);
659
    }
660
661
662
    /**
663
     * Returns the fully qualified name of the class that the specified class extends.
664
     *
665
     * Returns an empty string if the class does not extend another class or if
666
     * the class name could not be reliably inferred.
667
     *
668
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
669
     * @param int                  $stackPtr  The position of a T_CLASS token.
670
     *
671
     * @return string
672
     */
673
    public function getFQExtendedClassName(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
674
    {
675
        $tokens = $phpcsFile->getTokens();
676
677
        // Check for the existence of the token.
678
        if (isset($tokens[$stackPtr]) === false) {
679
            return '';
680
        }
681
682
        if ($tokens[$stackPtr]['code'] !== T_CLASS) {
683
            return '';
684
        }
685
686
        $extends = $phpcsFile->findExtendedClassName($stackPtr);
687
        if (empty($extends) || is_string($extends) === false) {
688
            return '';
689
        }
690
691
        return $this->getFQName($phpcsFile, $stackPtr, $extends);
692
    }
693
694
695
    /**
696
     * Returns the class name for the static usage of a class.
697
     * This can be a call to a method, the use of a property or constant.
698
     *
699
     * Returns an empty string if the class name could not be reliably inferred.
700
     *
701
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
702
     * @param int                  $stackPtr  The position of a T_NEW token.
703
     *
704
     * @return string
705
     */
706
    public function getFQClassNameFromDoubleColonToken(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
707
    {
708
        $tokens = $phpcsFile->getTokens();
709
710
        // Check for the existence of the token.
711
        if (isset($tokens[$stackPtr]) === false) {
712
            return '';
713
        }
714
715
        if ($tokens[$stackPtr]['code'] !== T_DOUBLE_COLON) {
716
            return '';
717
        }
718
719
        // Nothing to do if previous token is a variable as we don't know where it was defined.
720
        if ($tokens[$stackPtr - 1]['code'] === T_VARIABLE) {
721
            return '';
722
        }
723
724
        // Nothing to do if 'parent' or 'static' as we don't know how far the class tree extends.
725
        if (in_array($tokens[$stackPtr - 1]['code'], array(T_PARENT, T_STATIC), true)) {
726
            return '';
727
        }
728
729
        // Get the classname from the class declaration if self is used.
730
        if ($tokens[$stackPtr - 1]['code'] === T_SELF) {
731
            $classDeclarationPtr = $phpcsFile->findPrevious(T_CLASS, $stackPtr - 1);
732
            if ($classDeclarationPtr === false) {
733
                return '';
734
            }
735
            $className = $phpcsFile->getDeclarationName($classDeclarationPtr);
736
            return $this->getFQName($phpcsFile, $classDeclarationPtr, $className);
737
        }
738
739
        $find = array(
740
                 T_NS_SEPARATOR,
741
                 T_STRING,
742
                 T_NAMESPACE,
743
                 T_WHITESPACE,
744
                );
745
746
        $start     = ($phpcsFile->findPrevious($find, $stackPtr - 1, null, true, null, true) + 1);
747
        $className = $phpcsFile->getTokensAsString($start, ($stackPtr - $start));
748
        $className = trim($className);
749
750
        return $this->getFQName($phpcsFile, $stackPtr, $className);
751
    }
752
753
754
    /**
755
     * Get the Fully Qualified name for a class/function/constant etc.
756
     *
757
     * Checks if a class/function/constant name is already fully qualified and
758
     * if not, enrich it with the relevant namespace information.
759
     *
760
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
761
     * @param int                  $stackPtr  The position of the token.
762
     * @param string               $name      The class / function / constant name.
763
     *
764
     * @return string
765
     */
766
    public function getFQName(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $name)
767
    {
768
        if (strpos($name, '\\' ) === 0) {
769
            // Already fully qualified.
770
            return $name;
771
        }
772
773
        // Remove the namespace keyword if used.
774
        if (strpos($name, 'namespace\\') === 0) {
775
            $name = substr($name, 10);
776
        }
777
778
        $namespace = $this->determineNamespace($phpcsFile, $stackPtr);
779
780
        if ($namespace === '') {
781
            return '\\' . $name;
782
        }
783
        else {
784
            return '\\' . $namespace . '\\' . $name;
785
        }
786
    }
787
788
789
    /**
790
     * Is the class/function/constant name namespaced or global ?
791
     *
792
     * @param string $FQName Fully Qualified name of a class, function etc.
793
     *                       I.e. should always start with a `\` !
794
     *
795
     * @return bool True if namespaced, false if global.
796
     */
797
    public function isNamespaced($FQName) {
798
        if (strpos($FQName, '\\') !== 0) {
799
            throw new PHP_CodeSniffer_Exception('$FQName must be a fully qualified name');
800
        }
801
802
        return (strpos(substr($FQName, 1), '\\') !== false);
803
    }
804
805
806
    /**
807
     * Determine the namespace name an arbitrary token lives in.
808
     *
809
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
810
     * @param int                  $stackPtr  The token position for which to determine the namespace.
811
     *
812
     * @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
813
     */
814
    public function determineNamespace(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
815
    {
816
        $tokens = $phpcsFile->getTokens();
817
818
        // Check for the existence of the token.
819
        if (isset($tokens[$stackPtr]) === false) {
820
            return '';
821
        }
822
823
        // Check for scoped namespace {}.
824
        if (empty($tokens[$stackPtr]['conditions']) === false) {
825
            foreach ($tokens[$stackPtr]['conditions'] as $pointer => $type) {
826
                if ($type === T_NAMESPACE) {
827
                    $namespace = $this->getDeclaredNamespaceName($phpcsFile, $pointer);
828
                    if ($namespace !== false) {
829
                        return $namespace;
830
                    }
831
                    break; // Nested namespaces is not possible.
832
                }
833
            }
834
        }
835
836
        /*
837
         * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
838
         * Keeping in mind that:
839
         * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
840
         * - the namespace keyword can also be used as part of a function/method call and such.
841
         * - that a non-named namespace resolves to the global namespace.
842
         */
843
        $previousNSToken = $stackPtr;
844
        $namespace       = false;
845
        do {
846
            $previousNSToken = $phpcsFile->findPrevious(T_NAMESPACE, $previousNSToken -1);
847
848
            // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
849
            if (empty($tokens[$previousNSToken]['scope_condition']) === false && $tokens[$previousNSToken]['scope_condition'] = $previousNSToken) {
850
                break;
851
            }
852
            $namespace = $this->getDeclaredNamespaceName($phpcsFile, $previousNSToken);
853
854
        } while ($namespace === false && $previousNSToken !== false);
855
856
        // If we still haven't got a namespace, return an empty string.
857
        if ($namespace === false) {
858
            return '';
859
        }
860
        else {
861
            return $namespace;
862
        }
863
    }
864
865
    /**
866
     * Get the complete namespace name for a namespace declaration.
867
     *
868
     * For hierarchical namespaces, the name will be composed of several tokens,
869
     * i.e. MyProject\Sub\Level which will be returned together as one string.
870
     *
871
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
872
     * @param int|bool             $stackPtr  The position of a T_NAMESPACE token.
873
     *
874
     * @return string|false Namespace name or false if not a namespace declaration.
875
     *                      Namespace name can be an empty string for global namespace declaration.
876
     */
877
    public function getDeclaredNamespaceName(PHP_CodeSniffer_File $phpcsFile, $stackPtr )
878
    {
879
        $tokens = $phpcsFile->getTokens();
880
881
        // Check for the existence of the token.
882
        if ($stackPtr === false || isset($tokens[$stackPtr]) === false) {
883
            return false;
884
        }
885
886
        if ($tokens[$stackPtr]['code'] !== T_NAMESPACE) {
887
            return false;
888
        }
889
890
        if ($tokens[$stackPtr + 1]['code'] === T_NS_SEPARATOR) {
891
            // Not a namespace declaration, but use of, i.e. namespace\someFunction();
892
            return false;
893
        }
894
895
        $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
896
        if ($tokens[$nextToken]['code'] === T_OPEN_CURLY_BRACKET) {
897
            // Declaration for global namespace when using multiple namespaces in a file.
898
            // I.e.: namespace {}
899
            return '';
900
        }
901
902
        // Ok, this should be a namespace declaration, so get all the parts together.
903
        $validTokens = array(
904
                        T_STRING,
905
                        T_NS_SEPARATOR,
906
                        T_WHITESPACE,
907
                       );
908
909
        $namespaceName = '';
910
        while(in_array($tokens[$nextToken]['code'], $validTokens, true) === true) {
911
            $namespaceName .= trim($tokens[$nextToken]['content']);
912
            $nextToken++;
913
        }
914
915
        return $namespaceName;
916
    }
917
918
919
    /**
920
     * Returns the method parameters for the specified T_FUNCTION token.
921
     *
922
     * Each parameter is in the following format:
923
     *
924
     * <code>
925
     *   0 => array(
926
     *         'name'              => '$var',  // The variable name.
927
     *         'pass_by_reference' => false,   // Passed by reference.
928
     *         'type_hint'         => string,  // Type hint for array or custom type
929
     *         'nullable_type'     => bool,    // Whether the type given in the type hint is nullable
930
     *         'type_hint'         => string,  // Type hint for array or custom type
931
     *         'raw'               => string,  // Raw content of the tokens for the parameter
932
     *        )
933
     * </code>
934
     *
935
     * Parameters with default values have an additional array index of
936
     * 'default' with the value of the default as a string.
937
     *
938
     * {@internal Duplicate of same method as contained in the `PHP_CodeSniffer_File`
939
     * class, but with some improvements which will probably be introduced in
940
     * PHPCS 2.7.1/2.8. {@see https://github.com/squizlabs/PHP_CodeSniffer/pull/1117}
941
     * and {@see https://github.com/squizlabs/PHP_CodeSniffer/pull/1193}
942
     *
943
     * Once the minimum supported PHPCS version for this sniff library goes beyond
944
     * that, this method can be removed and calls to it replaced with
945
     * `$phpcsFile->getMethodParameters($stackPtr)` calls.
946
     *
947
     * Last synced with PHPCS version: PHPCS 2.7.}}
948
     *
949
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
950
     * @param int                  $stackPtr  The position in the stack of the
951
     *                                        T_FUNCTION token to acquire the
952
     *                                        parameters for.
953
     *
954
     * @return array|false
955
     * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of
956
     *                                   type T_FUNCTION.
957
     */
958
    public function getMethodParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
959
    {
960
        $tokens = $phpcsFile->getTokens();
961
962
        // Check for the existence of the token.
963
        if (isset($tokens[$stackPtr]) === false) {
964
            return false;
965
        }
966
967
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION) {
968
            throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION');
969
        }
970
971
        $opener = $tokens[$stackPtr]['parenthesis_opener'];
972
        $closer = $tokens[$stackPtr]['parenthesis_closer'];
973
974
        $vars            = array();
975
        $currVar         = null;
976
        $paramStart      = ($opener + 1);
977
        $defaultStart    = null;
978
        $paramCount      = 0;
979
        $passByReference = false;
980
        $variableLength  = false;
981
        $typeHint        = '';
982
        $nullableType    = false;
983
984
        for ($i = $paramStart; $i <= $closer; $i++) {
985
            // Check to see if this token has a parenthesis or bracket opener. If it does
986
            // it's likely to be an array which might have arguments in it. This
987
            // could cause problems in our parsing below, so lets just skip to the
988
            // end of it.
989 View Code Duplication
            if (isset($tokens[$i]['parenthesis_opener']) === true) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
990
                // Don't do this if it's the close parenthesis for the method.
991
                if ($i !== $tokens[$i]['parenthesis_closer']) {
992
                    $i = ($tokens[$i]['parenthesis_closer'] + 1);
993
                }
994
            }
995
996 View Code Duplication
            if (isset($tokens[$i]['bracket_opener']) === true) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
997
                // Don't do this if it's the close parenthesis for the method.
998
                if ($i !== $tokens[$i]['bracket_closer']) {
999
                    $i = ($tokens[$i]['bracket_closer'] + 1);
1000
                }
1001
            }
1002
1003
            switch ($tokens[$i]['code']) {
1004
            case T_BITWISE_AND:
1005
                $passByReference = true;
1006
                break;
1007
            case T_VARIABLE:
1008
                $currVar = $i;
1009
                break;
1010
            case T_ELLIPSIS:
1011
                $variableLength = true;
1012
                break;
1013
            case T_ARRAY_HINT:
1014
            case T_CALLABLE:
1015
                $typeHint = $tokens[$i]['content'];
1016
                break;
1017
            case T_SELF:
1018
            case T_PARENT:
1019 View Code Duplication
            case T_STATIC:
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1020
                // Self is valid, the others invalid, but were probably intended as type hints.
1021
                if ($defaultStart === null) {
1022
                    $typeHint = $tokens[$i]['content'];
1023
                }
1024
                break;
1025
            case T_STRING:
1026
                // This is a string, so it may be a type hint, but it could
1027
                // also be a constant used as a default value.
1028
                $prevComma = false;
1029 View Code Duplication
                for ($t = $i; $t >= $opener; $t--) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1030
                    if ($tokens[$t]['code'] === T_COMMA) {
1031
                        $prevComma = $t;
1032
                        break;
1033
                    }
1034
                }
1035
1036
                if ($prevComma !== false) {
1037
                    $nextEquals = false;
1038 View Code Duplication
                    for ($t = $prevComma; $t < $i; $t++) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1039
                        if ($tokens[$t]['code'] === T_EQUAL) {
1040
                            $nextEquals = $t;
1041
                            break;
1042
                        }
1043
                    }
1044
1045
                    if ($nextEquals !== false) {
1046
                        break;
1047
                    }
1048
                }
1049
1050
                if ($defaultStart === null) {
1051
                    $typeHint .= $tokens[$i]['content'];
1052
                }
1053
                break;
1054 View Code Duplication
            case T_NS_SEPARATOR:
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1055
                // Part of a type hint or default value.
1056
                if ($defaultStart === null) {
1057
                    $typeHint .= $tokens[$i]['content'];
1058
                }
1059
                break;
1060
            case T_INLINE_THEN:
1061
                if ($defaultStart === null) {
1062
                    $nullableType = true;
1063
                    $typeHint    .= $tokens[$i]['content'];
1064
                }
1065
                break;
1066
            case T_CLOSE_PARENTHESIS:
1067
            case T_COMMA:
1068
                // If it's null, then there must be no parameters for this
1069
                // method.
1070
                if ($currVar === null) {
1071
                    continue;
1072
                }
1073
1074
                $vars[$paramCount]         = array();
1075
                $vars[$paramCount]['name'] = $tokens[$currVar]['content'];
1076
1077
                if ($defaultStart !== null) {
1078
                    $vars[$paramCount]['default']
1079
                        = $phpcsFile->getTokensAsString(
1080
                            $defaultStart,
1081
                            ($i - $defaultStart)
1082
                        );
1083
                }
1084
1085
                $rawContent = trim($phpcsFile->getTokensAsString($paramStart, ($i - $paramStart)));
1086
1087
                $vars[$paramCount]['pass_by_reference'] = $passByReference;
1088
                $vars[$paramCount]['variable_length']   = $variableLength;
1089
                $vars[$paramCount]['type_hint']         = $typeHint;
1090
                $vars[$paramCount]['nullable_type']     = $nullableType;
1091
                $vars[$paramCount]['raw']               = $rawContent;
1092
1093
                // Reset the vars, as we are about to process the next parameter.
1094
                $defaultStart    = null;
1095
                $paramStart      = ($i + 1);
1096
                $passByReference = false;
1097
                $variableLength  = false;
1098
                $typeHint        = '';
1099
                $nullableType    = false;
1100
1101
                $paramCount++;
1102
                break;
1103
            case T_EQUAL:
1104
                $defaultStart = ($i + 1);
1105
                break;
1106
            }//end switch
1107
        }//end for
1108
1109
        return $vars;
1110
1111
    }//end getMethodParameters()
1112
1113
1114
    /**
1115
     * Get the hash algorithm name from the parameter in a hash function call.
1116
     *
1117
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
1118
     * @param int                  $stackPtr  The position of the T_STRING function token.
1119
     *
1120
     * @return string|false The algorithm name without quotes if this was a relevant hash
1121
     *                      function call or false if it was not.
1122
     */
1123
    public function getHashAlgorithmParameter(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
1124
    {
1125
        $tokens = $phpcsFile->getTokens();
1126
1127
        // Check for the existence of the token.
1128
        if (isset($tokens[$stackPtr]) === false) {
1129
            return false;
1130
        }
1131
1132
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
1133
            return false;
1134
        }
1135
1136
        $functionName   = $tokens[$stackPtr]['content'];
1137
        $functionNameLc = strtolower($functionName);
1138
1139
        // Bow out if not one of the functions we're targetting.
1140
        if (isset($this->hashAlgoFunctions[$functionNameLc]) === false) {
1141
            return false;
1142
        }
1143
1144
        // Get the parameter from the function call which should contain the algorithm name.
1145
        $algoParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->hashAlgoFunctions[$functionNameLc]);
1146
        if ($algoParam === false) {
1147
            return false;
1148
        }
1149
1150
        /**
1151
         * Algorithm is a text string, so we need to remove the quotes.
1152
         */
1153
        $algo = strtolower(trim($algoParam['raw']));
1154
        $algo = $this->stripQuotes($algo);
1155
1156
        return $algo;
1157
    }
1158
1159
}//end class
1160