Completed
Push — master ( 660809...90ada1 )
by Wim
02:31
created

Sniff.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
/* The testVersion configuration variable may be in any of the following formats:
26
 * 1) Omitted/empty, in which case no version is specified.  This effectively
27
 *    disables all the checks provided by this standard.
28
 * 2) A single PHP version number, e.g. "5.4" in which case the standard checks that
29
 *    the code will run on that version of PHP (no deprecated features or newer
30
 *    features being used).
31
 * 3) A range, e.g. "5.0-5.5", in which case the standard checks the code will run
32
 *    on all PHP versions in that range, and that it doesn't use any features that
33
 *    were deprecated by the final version in the list, or which were not available
34
 *    for the first version in the list.
35
 * PHP version numbers should always be in Major.Minor format.  Both "5", "5.3.2"
36
 * would be treated as invalid, and ignored.
37
 * This standard doesn't support checking against PHP4, so the minimum version that
38
 * is recognised is "5.0".
39
 */
40
41
    private function getTestVersion()
42
    {
43
        /**
44
         * var $arrTestVersions will hold an array containing min/max version of PHP
45
         *   that we are checking against (see above).  If only a single version
46
         *   number is specified, then this is used as both the min and max.
47
         */
48
        static $arrTestVersions = array();
49
50
        $testVersion = trim(PHP_CodeSniffer::getConfigData('testVersion'));
51
52
        if (!isset($arrTestVersions[$testVersion]) && !empty($testVersion)) {
53
54
            $arrTestVersions[$testVersion] = array(null, null);
55
            if (preg_match('/^\d+\.\d+$/', $testVersion)) {
56
                $arrTestVersions[$testVersion] = array($testVersion, $testVersion);
57
            }
58
            elseif (preg_match('/^(\d+\.\d+)\s*-\s*(\d+\.\d+)$/', $testVersion,
59
                               $matches))
60
            {
61
                if (version_compare($matches[1], $matches[2], '>')) {
62
                    trigger_error("Invalid range in testVersion setting: '"
63
                                  . $testVersion . "'", E_USER_WARNING);
64
                }
65
                else {
66
                    $arrTestVersions[$testVersion] = array($matches[1], $matches[2]);
67
                }
68
            }
69
            elseif (!$testVersion == '') {
70
                trigger_error("Invalid testVersion setting: '" . $testVersion
71
                              . "'", E_USER_WARNING);
72
            }
73
        }
74
75
        if (isset($arrTestVersions[$testVersion])) {
76
            return $arrTestVersions[$testVersion];
77
        }
78
        else {
79
            return array(null, null);
80
        }
81
    }
82
83 View Code Duplication
    public function supportsAbove($phpVersion)
0 ignored issues
show
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...
84
    {
85
        $testVersion = $this->getTestVersion();
86
        $testVersion = $testVersion[1];
87
88
        if (is_null($testVersion)
89
            || version_compare($testVersion, $phpVersion) >= 0
90
        ) {
91
            return true;
92
        } else {
93
            return false;
94
        }
95
    }//end supportsAbove()
96
97 View Code Duplication
    public function supportsBelow($phpVersion)
0 ignored issues
show
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...
98
    {
99
        $testVersion = $this->getTestVersion();
100
        $testVersion = $testVersion[0];
101
102
        if (!is_null($testVersion)
103
            && version_compare($testVersion, $phpVersion) <= 0
104
        ) {
105
            return true;
106
        } else {
107
            return false;
108
        }
109
    }//end supportsBelow()
110
111
112
    /**
113
     * Strip quotes surrounding an arbitrary string.
114
     *
115
     * Intended for use with the content of a T_CONSTANT_ENCAPSED_STRING.
116
     *
117
     * @param string $string The raw string.
118
     *
119
     * @return string String without quotes around it.
120
     */
121
    public function stripQuotes($string) {
122
        return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
123
    }
124
125
126
    /**
127
     * Returns the name(s) of the interface(s) that the specified class implements.
128
     *
129
     * Returns FALSE on error or if there are no implemented interface names.
130
     *
131
     * {@internal Duplicate of same method as introduced in PHPCS 2.7.
132
     * Once the minimum supported PHPCS version for this sniff library goes beyond
133
     * that, this method can be removed and call to it replaced with
134
     * `$phpcsFile->findImplementedInterfaceNames($stackPtr)` calls.}}
135
     *
136
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
137
     * @param int                  $stackPtr  The position of the class token.
138
     *
139
     * @return array|false
140
     */
141
    public function findImplementedInterfaceNames(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
142
    {
143
        if (method_exists($phpcsFile, 'findImplementedInterfaceNames')) {
144
            return $phpcsFile->findImplementedInterfaceNames($stackPtr);
145
        }
146
147
        $tokens = $phpcsFile->getTokens();
148
149
        // Check for the existence of the token.
150
        if (isset($tokens[$stackPtr]) === false) {
151
            return false;
152
        }
153
154
        if ($tokens[$stackPtr]['code'] !== T_CLASS) {
155
            return false;
156
        }
157
158
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
159
            return false;
160
        }
161
162
        $classOpenerIndex = $tokens[$stackPtr]['scope_opener'];
163
        $implementsIndex  = $phpcsFile->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
164
        if ($implementsIndex === false) {
165
            return false;
166
        }
167
168
        $find = array(
169
                 T_NS_SEPARATOR,
170
                 T_STRING,
171
                 T_WHITESPACE,
172
                 T_COMMA,
173
                );
174
175
        $end  = $phpcsFile->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
176
        $name = $phpcsFile->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
177
        $name = trim($name);
178
179
        if ($name === '') {
180
            return false;
181
        } else {
182
            $names = explode(',', $name);
183
            $names = array_map('trim', $names);
184
            return $names;
185
        }
186
187
    }//end findImplementedInterfaceNames()
188
189
190
    /**
191
     * Checks if a function call has parameters.
192
     *
193
     * Expects to be passed the T_STRING stack pointer for the function call.
194
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
195
     *
196
     * @link https://github.com/wimg/PHPCompatibility/issues/120
197
     * @link https://github.com/wimg/PHPCompatibility/issues/152
198
     *
199
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
200
     * @param int                  $stackPtr  The position of the function call token.
201
     *
202
     * @return bool
203
     */
204
    public function doesFunctionCallHaveParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
205
    {
206
        $tokens = $phpcsFile->getTokens();
207
208
        // Check for the existence of the token.
209
        if (isset($tokens[$stackPtr]) === false) {
210
            return false;
211
        }
212
213
        if ($tokens[$stackPtr]['code'] !== T_STRING) {
214
            return false;
215
        }
216
217
        // Next non-empty token should be the open parenthesis.
218
        $openParenthesis = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
219
        if ($openParenthesis === false || $tokens[$openParenthesis]['code'] !== T_OPEN_PARENTHESIS) {
220
            return false;
221
        }
222
223
        if (isset($tokens[$openParenthesis]['parenthesis_closer']) === false) {
224
            return false;
225
        }
226
227
        $closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
228
        $nextNonEmpty     = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $openParenthesis + 1, $closeParenthesis + 1, true);
229
230
        if ($nextNonEmpty === $closeParenthesis) {
231
            // No parameters.
232
            return false;
233
        }
234
235
        return true;
236
    }
237
238
239
    /**
240
     * Count the number of parameters a function call has been passed.
241
     *
242
     * Expects to be passed the T_STRING stack pointer for the function call.
243
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
244
     *
245
     * @link https://github.com/wimg/PHPCompatibility/issues/111
246
     * @link https://github.com/wimg/PHPCompatibility/issues/114
247
     * @link https://github.com/wimg/PHPCompatibility/issues/151
248
     *
249
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
250
     * @param int                  $stackPtr  The position of the function call token.
251
     *
252
     * @return int
253
     */
254
    public function getFunctionCallParameterCount(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
255
    {
256
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
257
            return 0;
258
        }
259
260
        return count($this->getFunctionCallParameters($phpcsFile, $stackPtr));
261
    }
262
263
264
    /**
265
     * Get information on all parameters passed to a function call.
266
     *
267
     * Expects to be passed the T_STRING stack pointer for the function call.
268
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
269
     *
270
     * Will return an multi-dimentional array with the start token pointer, end token
271
     * pointer and raw parameter value for all parameters. Index will be 1-based.
272
     * If no parameters are found, will return an empty array.
273
     *
274
     * @param PHP_CodeSniffer_File $phpcsFile     The file being scanned.
275
     * @param int                  $stackPtr      The position of the function call token.
276
     *
277
     * @return array
278
     */
279
    public function getFunctionCallParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
280
    {
281
        if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
282
            return array();
283
        }
284
285
        // Ok, we know we have a T_STRING with parameters and valid open & close parenthesis.
286
        $tokens = $phpcsFile->getTokens();
287
288
        $openParenthesis  = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
289
        $closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
290
291
        // Which nesting level is the one we are interested in ?
292
        $nestedParenthesisCount = 1;
293 View Code Duplication
        if (isset($tokens[$openParenthesis]['nested_parenthesis'])) {
1 ignored issue
show
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...
294
            $nestedParenthesisCount = count($tokens[$openParenthesis]['nested_parenthesis']) + 1;
295
        }
296
297
        $parameters = array();
298
        $nextComma  = $openParenthesis;
299
        $paramStart = $openParenthesis + 1;
300
        $cnt        = 1;
301
        while ($nextComma = $phpcsFile->findNext(array(T_COMMA, T_CLOSE_PARENTHESIS, T_OPEN_SHORT_ARRAY), $nextComma + 1, $closeParenthesis + 1)) {
302
            // Ignore anything within short array definition brackets.
303
            if (
304
                $tokens[$nextComma]['type'] === 'T_OPEN_SHORT_ARRAY'
305
                &&
306
                ( isset($tokens[$nextComma]['bracket_opener']) && $tokens[$nextComma]['bracket_opener'] === $nextComma )
307
                &&
308
                isset($tokens[$nextComma]['bracket_closer'])
309
            ) {
310
                // Skip forward to the end of the short array definition.
311
                $nextComma = $tokens[$nextComma]['bracket_closer'];
312
                continue;
313
            }
314
315
            // Ignore comma's at a lower nesting level.
316
            if (
317
                $tokens[$nextComma]['type'] === 'T_COMMA'
318
                &&
319
                isset($tokens[$nextComma]['nested_parenthesis'])
320
                &&
321
                count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount
322
            ) {
323
                continue;
324
            }
325
326
            // Ignore closing parenthesis if not 'ours'.
327
            if ($tokens[$nextComma]['type'] === 'T_CLOSE_PARENTHESIS' && $nextComma !== $closeParenthesis) {
328
                continue;
329
            }
330
331
            // Ok, we've reached the end of the parameter.
332
            $parameters[$cnt]['start'] = $paramStart;
333
            $parameters[$cnt]['end']   = $nextComma - 1;
334
            $parameters[$cnt]['raw']   = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart)));
335
336
            // Check if there are more tokens before the closing parenthesis.
337
            // Prevents code like the following from setting a third parameter:
338
            // 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...
339
            $hasNextParam = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $nextComma + 1, $closeParenthesis, true, null, true);
340
            if ($hasNextParam === false) {
341
                break;
342
            }
343
344
            // Prepare for the next parameter.
345
            $paramStart = $nextComma + 1;
346
            $cnt++;
347
        }
348
349
        return $parameters;
350
    }
351
352
353
    /**
354
     * Get information on a specific parameter passed to a function call.
355
     *
356
     * Expects to be passed the T_STRING stack pointer for the function call.
357
     * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
358
     *
359
     * Will return a array with the start token pointer, end token pointer and the raw value
360
     * of the parameter at a specific offset.
361
     * If the specified parameter is not found, will return false.
362
     *
363
     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
364
     * @param int                  $stackPtr    The position of the function call token.
365
     * @param int                  $paramOffset The 1-based index position of the parameter to retrieve.
366
     *
367
     * @return array|false
368
     */
369
    public function getFunctionCallParameter(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $paramOffset)
370
    {
371
        $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
372
373
        if (isset($parameters[$paramOffset]) === false) {
374
            return false;
375
        }
376
        else {
377
            return $parameters[$paramOffset];
378
        }
379
    }
380
381
382
    /**
383
     * Verify whether a token is within a scoped condition.
384
     *
385
     * If the optional $validScopes parameter has been passed, the function
386
     * will check that the token has at least one condition which is of a
387
     * type defined in $validScopes.
388
     *
389
     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
390
     * @param int                  $stackPtr    The position of the token.
391
     * @param array|int            $validScopes Optional. Array of valid scopes
392
     *                                          or int value of a valid scope.
393
     *
394
     * @return bool Without the optional $scopeTypes: True if within a scope, false otherwise.
395
     *              If the $scopeTypes are set: True if *one* of the conditions is a
396
     *              valid scope, false otherwise.
397
     */
398
    public function tokenHasScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $validScopes = null)
399
    {
400
        $tokens = $phpcsFile->getTokens();
401
402
        // Check for the existence of the token.
403
        if (isset($tokens[$stackPtr]) === false) {
404
            return false;
405
        }
406
407
        // No conditions = no scope.
408
        if (empty($tokens[$stackPtr]['conditions'])) {
409
            return false;
410
        }
411
412
        // Ok, there are conditions, do we have to check for specific ones ?
413
        if (isset($validScopes) === false) {
414
            return true;
415
        }
416
417
        if (is_int($validScopes)) {
418
            // Received an integer, so cast to array.
419
            $validScopes = (array) $validScopes;
420
        }
421
422
        if (empty($validScopes) || is_array($validScopes) === false) {
423
            // No valid scope types received, so will not comply.
424
            return false;
425
        }
426
427
        // Check for required scope types.
428
        foreach ($tokens[$stackPtr]['conditions'] as $pointer => $type) {
429
            if (in_array($type, $validScopes, true)) {
430
                return true;
431
            }
432
        }
433
434
        return false;
435
    }
436
437
438
    /**
439
     * Verify whether a token is within a class scope.
440
     *
441
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
442
     * @param int                  $stackPtr  The position of the token.
443
     * @param bool                 $strict    Whether to strictly check for the T_CLASS
444
     *                                        scope or also accept interfaces and traits
445
     *                                        as scope.
446
     *
447
     * @return bool True if within class scope, false otherwise.
448
     */
449
    public function inClassScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $strict = true)
450
    {
451
        $validScopes = array(T_CLASS);
452
        if ($strict === false) {
453
            $validScopes[] = T_INTERFACE;
454
            $validScopes[] = T_TRAIT;
455
        }
456
457
        return $this->tokenHasScope($phpcsFile, $stackPtr, $validScopes);
458
    }
459
460
461
    /**
462
     * Returns the fully qualified class name for a new class instantiation.
463
     *
464
     * Returns an empty string if the class name could not be reliably inferred.
465
     *
466
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
467
     * @param int                  $stackPtr  The position of a T_NEW token.
468
     *
469
     * @return string
470
     */
471
    public function getFQClassNameFromNewToken(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
472
    {
473
        $tokens = $phpcsFile->getTokens();
474
475
        // Check for the existence of the token.
476
        if (isset($tokens[$stackPtr]) === false) {
477
            return '';
478
        }
479
480
        if ($tokens[$stackPtr]['code'] !== T_NEW) {
481
            return '';
482
        }
483
484
        $find = array(
485
                 T_NS_SEPARATOR,
486
                 T_STRING,
487
                 T_NAMESPACE,
488
                 T_WHITESPACE,
489
                );
490
491
        $start = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
492
        // Bow out if the next token is a variable as we don't know where it was defined.
493
        if ($tokens[$start]['code'] === T_VARIABLE) {
494
            return '';
495
        }
496
497
        $end       = $phpcsFile->findNext($find, ($start + 1), null, true, null, true);
498
        $className = $phpcsFile->getTokensAsString($start, ($end - $start));
499
        $className = trim($className);
500
501
        return $this->getFQName($phpcsFile, $stackPtr, $className);
502
    }
503
504
505
    /**
506
     * Returns the fully qualified name of the class that the specified class extends.
507
     *
508
     * Returns an empty string if the class does not extend another class or if
509
     * the class name could not be reliably inferred.
510
     *
511
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
512
     * @param int                  $stackPtr  The position of a T_CLASS token.
513
     *
514
     * @return string
515
     */
516
    public function getFQExtendedClassName(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
517
    {
518
        $tokens = $phpcsFile->getTokens();
519
520
        // Check for the existence of the token.
521
        if (isset($tokens[$stackPtr]) === false) {
522
            return '';
523
        }
524
525
        if ($tokens[$stackPtr]['code'] !== T_CLASS) {
526
            return '';
527
        }
528
529
        $extends = $phpcsFile->findExtendedClassName($stackPtr);
530
        if (empty($extends) || is_string($extends) === false) {
531
            return '';
532
        }
533
534
        return $this->getFQName($phpcsFile, $stackPtr, $extends);
535
    }
536
537
538
    /**
539
     * Returns the class name for the static usage of a class.
540
     * This can be a call to a method, the use of a property or constant.
541
     *
542
     * Returns an empty string if the class name could not be reliably inferred.
543
     *
544
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
545
     * @param int                  $stackPtr  The position of a T_NEW token.
546
     *
547
     * @return string
548
     */
549
    public function getFQClassNameFromDoubleColonToken(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
550
    {
551
        $tokens = $phpcsFile->getTokens();
552
553
        // Check for the existence of the token.
554
        if (isset($tokens[$stackPtr]) === false) {
555
            return '';
556
        }
557
558
        if ($tokens[$stackPtr]['code'] !== T_DOUBLE_COLON) {
559
            return '';
560
        }
561
562
        // Nothing to do if previous token is a variable as we don't know where it was defined.
563
        if ($tokens[$stackPtr - 1]['code'] === T_VARIABLE) {
564
            return '';
565
        }
566
567
        // Nothing to do if 'parent' or 'static' as we don't know how far the class tree extends.
568
        if (in_array($tokens[$stackPtr - 1]['code'], array(T_PARENT, T_STATIC), true)) {
569
            return '';
570
        }
571
572
        // Get the classname from the class declaration if self is used.
573
        if ($tokens[$stackPtr - 1]['code'] === T_SELF) {
574
            $classDeclarationPtr = $phpcsFile->findPrevious(T_CLASS, $stackPtr - 1);
575
            if ($classDeclarationPtr === false) {
576
                return '';
577
            }
578
            $className = $phpcsFile->getDeclarationName($classDeclarationPtr);
579
            return $this->getFQName($phpcsFile, $classDeclarationPtr, $className);
580
        }
581
582
        $find = array(
583
                 T_NS_SEPARATOR,
584
                 T_STRING,
585
                 T_NAMESPACE,
586
                 T_WHITESPACE,
587
                );
588
589
        $start     = ($phpcsFile->findPrevious($find, $stackPtr - 1, null, true, null, true) + 1);
590
        $className = $phpcsFile->getTokensAsString($start, ($stackPtr - $start));
591
        $className = trim($className);
592
593
        return $this->getFQName($phpcsFile, $stackPtr, $className);
594
    }
595
596
597
    /**
598
     * Get the Fully Qualified name for a class/function/constant etc.
599
     *
600
     * Checks if a class/function/constant name is already fully qualified and
601
     * if not, enrich it with the relevant namespace information.
602
     *
603
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
604
     * @param int                  $stackPtr  The position of the token.
605
     * @param string               $name      The class / function / constant name.
606
     *
607
     * @return string
608
     */
609
    public function getFQName(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $name)
610
    {
611
        if (strpos($name, '\\' ) === 0) {
612
            // Already fully qualified.
613
            return $name;
614
        }
615
616
        // Remove the namespace keyword if used.
617
        if (strpos($name, 'namespace\\') === 0) {
618
            $name = substr($name, 10);
619
        }
620
621
        $namespace = $this->determineNamespace($phpcsFile, $stackPtr);
622
623
        if ($namespace === '') {
624
            return '\\' . $name;
625
        }
626
        else {
627
            return '\\' . $namespace . '\\' . $name;
628
        }
629
    }
630
631
632
    /**
633
     * Is the class/function/constant name namespaced or global ?
634
     *
635
     * @param string $FQName Fully Qualified name of a class, function etc.
636
     *                       I.e. should always start with a `\` !
637
     *
638
     * @return bool True if namespaced, false if global.
639
     */
640
    public function isNamespaced($FQName) {
641
        if (strpos($FQName, '\\') !== 0) {
642
            throw new PHP_CodeSniffer_Exception('$FQName must be a fully qualified name');
643
        }
644
645
        return (strpos(substr($FQName, 1), '\\') !== false);
646
    }
647
648
649
    /**
650
     * Determine the namespace name an arbitrary token lives in.
651
     *
652
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
653
     * @param int                  $stackPtr  The token position for which to determine the namespace.
654
     *
655
     * @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
656
     */
657
    public function determineNamespace(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
        // Check for scoped namespace {}.
667
        if (empty($tokens[$stackPtr]['conditions']) === false) {
668
            foreach ($tokens[$stackPtr]['conditions'] as $pointer => $type) {
669
                if ($type === T_NAMESPACE) {
670
                    $namespace = $this->getDeclaredNamespaceName($phpcsFile, $pointer);
671
                    if ($namespace !== false) {
672
                        return $namespace;
673
                    }
674
                    break; // Nested namespaces is not possible.
675
                }
676
            }
677
        }
678
679
        /*
680
         * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
681
         * Keeping in mind that:
682
         * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
683
         * - the namespace keyword can also be used as part of a function/method call and such.
684
         * - that a non-named namespace resolves to the global namespace.
685
         */
686
        $previousNSToken = $stackPtr;
687
        $namespace       = false;
688
        do {
689
            $previousNSToken = $phpcsFile->findPrevious(T_NAMESPACE, $previousNSToken -1);
690
691
            // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
692
            if (empty($tokens[$previousNSToken]['scope_condition']) === false && $tokens[$previousNSToken]['scope_condition'] = $previousNSToken) {
693
                break;
694
            }
695
            $namespace = $this->getDeclaredNamespaceName($phpcsFile, $previousNSToken);
696
697
        } while ($namespace === false && $previousNSToken !== false);
698
699
        // If we still haven't got a namespace, return an empty string.
700
        if ($namespace === false) {
701
            return '';
702
        }
703
        else {
704
            return $namespace;
705
        }
706
    }
707
708
    /**
709
     * Get the complete namespace name for a namespace declaration.
710
     *
711
     * For hierarchical namespaces, the name will be composed of several tokens,
712
     * i.e. MyProject\Sub\Level which will be returned together as one string.
713
     *
714
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
715
     * @param int|bool             $stackPtr  The position of a T_NAMESPACE token.
716
     *
717
     * @return string|false Namespace name or false if not a namespace declaration.
718
     *                      Namespace name can be an empty string for global namespace declaration.
719
     */
720
    public function getDeclaredNamespaceName(PHP_CodeSniffer_File $phpcsFile, $stackPtr )
721
    {
722
        $tokens = $phpcsFile->getTokens();
723
724
        // Check for the existence of the token.
725
        if ($stackPtr === false || isset($tokens[$stackPtr]) === false) {
726
            return false;
727
        }
728
729
        if ($tokens[$stackPtr]['code'] !== T_NAMESPACE) {
730
            return false;
731
        }
732
733
        if ($tokens[$stackPtr + 1]['code'] === T_NS_SEPARATOR) {
734
            // Not a namespace declaration, but use of, i.e. namespace\someFunction();
735
            return false;
736
        }
737
738
        $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
739
        if ($tokens[$nextToken]['code'] === T_OPEN_CURLY_BRACKET) {
740
            // Declaration for global namespace when using multiple namespaces in a file.
741
            // I.e.: namespace {}
742
            return '';
743
        }
744
745
        // Ok, this should be a namespace declaration, so get all the parts together.
746
        $validTokens = array(
747
                        T_STRING,
748
                        T_NS_SEPARATOR,
749
                        T_WHITESPACE,
750
                       );
751
752
        $namespaceName = '';
753
        while(in_array($tokens[$nextToken]['code'], $validTokens, true) === true) {
754
            $namespaceName .= trim($tokens[$nextToken]['content']);
755
            $nextToken++;
756
        }
757
758
        return $namespaceName;
759
    }
760
761
762
    /**
763
     * Returns the method parameters for the specified T_FUNCTION token.
764
     *
765
     * Each parameter is in the following format:
766
     *
767
     * <code>
768
     *   0 => array(
769
     *         'name'              => '$var',  // The variable name.
770
     *         'pass_by_reference' => false,   // Passed by reference.
771
     *         'type_hint'         => string,  // Type hint for array or custom type
772
     *        )
773
     * </code>
774
     *
775
     * Parameters with default values have an additional array index of
776
     * 'default' with the value of the default as a string.
777
     *
778
     * {@internal Duplicate of same method as contained in the `PHP_CodeSniffer_File`
779
     * class, but with some improvements which were only introduced in PHPCS 2.7.
780
     * Once the minimum supported PHPCS version for this sniff library goes beyond
781
     * that, this method can be removed and calls to it replaced with
782
     * `$phpcsFile->getMethodParameters($stackPtr)` calls.}}
783
     *
784
     * @param PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
785
     * @param int $stackPtr The position in the stack of the T_FUNCTION token
786
     *                      to acquire the parameters for.
787
     *
788
     * @return array|false
789
     * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of
790
     *                                   type T_FUNCTION.
791
     */
792
    public function getMethodParameters(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
793
    {
794
        $tokens = $phpcsFile->getTokens();
795
796
        // Check for the existence of the token.
797
        if (isset($tokens[$stackPtr]) === false) {
798
            return false;
799
        }
800
801
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION) {
802
            throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION');
803
        }
804
805
        $opener = $tokens[$stackPtr]['parenthesis_opener'];
806
        $closer = $tokens[$stackPtr]['parenthesis_closer'];
807
808
        $vars            = array();
809
        $currVar         = null;
810
        $paramStart      = ($opener + 1);
811
        $defaultStart    = null;
812
        $paramCount      = 0;
813
        $passByReference = false;
814
        $variableLength  = false;
815
        $typeHint        = '';
816
817
        for ($i = $paramStart; $i <= $closer; $i++) {
818
            // Check to see if this token has a parenthesis or bracket opener. If it does
819
            // it's likely to be an array which might have arguments in it. This
820
            // could cause problems in our parsing below, so lets just skip to the
821
            // end of it.
822 View Code Duplication
            if (isset($tokens[$i]['parenthesis_opener']) === true) {
1 ignored issue
show
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...
823
                // Don't do this if it's the close parenthesis for the method.
824
                if ($i !== $tokens[$i]['parenthesis_closer']) {
825
                    $i = ($tokens[$i]['parenthesis_closer'] + 1);
826
                }
827
            }
828
829 View Code Duplication
            if (isset($tokens[$i]['bracket_opener']) === true) {
1 ignored issue
show
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...
830
                // Don't do this if it's the close parenthesis for the method.
831
                if ($i !== $tokens[$i]['bracket_closer']) {
832
                    $i = ($tokens[$i]['bracket_closer'] + 1);
833
                }
834
            }
835
836
            switch ($tokens[$i]['code']) {
837
            case T_BITWISE_AND:
838
                $passByReference = true;
839
                break;
840
            case T_VARIABLE:
841
                $currVar = $i;
842
                break;
843
            case T_ELLIPSIS:
844
                $variableLength = true;
845
                break;
846
            case T_ARRAY_HINT:
847
            case T_CALLABLE:
848
                $typeHint = $tokens[$i]['content'];
849
                break;
850
            case T_SELF:
851
            case T_PARENT:
852
            case T_STATIC:
853
                // Self is valid, the others invalid, but were probably intended as type hints.
854
                if (isset($defaultStart) === false) {
855
                    $typeHint = $tokens[$i]['content'];
856
                }
857
                break;
858
            case T_STRING:
859
                // This is a string, so it may be a type hint, but it could
860
                // also be a constant used as a default value.
861
                $prevComma = false;
862 View Code Duplication
                for ($t = $i; $t >= $opener; $t--) {
1 ignored issue
show
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...
863
                    if ($tokens[$t]['code'] === T_COMMA) {
864
                        $prevComma = $t;
865
                        break;
866
                    }
867
                }
868
869
                if ($prevComma !== false) {
870
                    $nextEquals = false;
871 View Code Duplication
                    for ($t = $prevComma; $t < $i; $t++) {
1 ignored issue
show
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...
872
                        if ($tokens[$t]['code'] === T_EQUAL) {
873
                            $nextEquals = $t;
874
                            break;
875
                        }
876
                    }
877
878
                    if ($nextEquals !== false) {
879
                        break;
880
                    }
881
                }
882
883
                if ($defaultStart === null) {
884
                    $typeHint .= $tokens[$i]['content'];
885
                }
886
                break;
887
            case T_NS_SEPARATOR:
888
                // Part of a type hint or default value.
889
                if ($defaultStart === null) {
890
                    $typeHint .= $tokens[$i]['content'];
891
                }
892
                break;
893
            case T_CLOSE_PARENTHESIS:
894
            case T_COMMA:
895
                // If it's null, then there must be no parameters for this
896
                // method.
897
                if ($currVar === null) {
898
                    continue;
899
                }
900
901
                $vars[$paramCount]         = array();
902
                $vars[$paramCount]['name'] = $tokens[$currVar]['content'];
903
904
                if ($defaultStart !== null) {
905
                    $vars[$paramCount]['default']
906
                        = $phpcsFile->getTokensAsString(
907
                            $defaultStart,
908
                            ($i - $defaultStart)
909
                        );
910
                }
911
912
                $rawContent = trim($phpcsFile->getTokensAsString($paramStart, ($i - $paramStart)));
913
914
                $vars[$paramCount]['pass_by_reference'] = $passByReference;
915
                $vars[$paramCount]['variable_length']   = $variableLength;
916
                $vars[$paramCount]['type_hint']         = $typeHint;
917
                $vars[$paramCount]['raw'] = $rawContent;
918
919
                // Reset the vars, as we are about to process the next parameter.
920
                $defaultStart    = null;
921
                $paramStart      = ($i + 1);
922
                $passByReference = false;
923
                $variableLength  = false;
924
                $typeHint        = '';
925
926
                $paramCount++;
927
                break;
928
            case T_EQUAL:
929
                $defaultStart = ($i + 1);
930
                break;
931
            }//end switch
932
        }//end for
933
934
        return $vars;
935
936
    }//end getMethodParameters()
937
938
}//end class
939