Failed Conditions
Push — feature/scrutinizer-send-pass-... ( 5c2dd9 )
by Juliette
02:12
created

NewClassesSniff::getItemArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
1
<?php
2
/**
3
 * \PHPCompatibility\Sniffs\PHP\NewClassesSniff.
4
 *
5
 * @category  PHP
6
 * @package   PHPCompatibility
7
 * @author    Wim Godden <[email protected]>
8
 * @copyright 2013 Cu.be Solutions bvba
9
 */
10
11
namespace PHPCompatibility\Sniffs\PHP;
12
13
use PHPCompatibility\AbstractNewFeatureSniff;
14
15
/**
16
 * \PHPCompatibility\Sniffs\PHP\NewClassesSniff.
17
 *
18
 * @category  PHP
19
 * @package   PHPCompatibility
20
 * @author    Wim Godden <[email protected]>
21
 * @copyright 2013 Cu.be Solutions bvba
22
 */
23
class NewClassesSniff extends AbstractNewFeatureSniff
24
{
25
26
    /**
27
     * A list of new classes, not present in older versions.
28
     *
29
     * The array lists : version number with false (not present) or true (present).
30
     * If's sufficient to list the first version where the class appears.
31
     *
32
     * @var array(string => array(string => bool))
33
     */
34
    protected $newClasses = array(
35
        'ArrayObject' => array(
36
            '4.4' => false,
37
            '5.0' => true,
38
        ),
39
        'ArrayIterator' => array(
40
            '4.4' => false,
41
            '5.0' => true,
42
        ),
43
        'CachingIterator' => array(
44
            '4.4' => false,
45
            '5.0' => true,
46
        ),
47
        'DirectoryIterator' => array(
48
            '4.4' => false,
49
            '5.0' => true,
50
        ),
51
        'RecursiveDirectoryIterator' => array(
52
            '4.4' => false,
53
            '5.0' => true,
54
        ),
55
        'RecursiveIteratorIterator' => array(
56
            '4.4' => false,
57
            '5.0' => true,
58
        ),
59
        'php_user_filter' => array(
60
            '4.4' => false,
61
            '5.0' => true,
62
        ),
63
        'tidy' => array(
64
            '4.4' => false,
65
            '5.0' => true,
66
        ),
67
68
        'SimpleXMLElement' => array(
69
            '5.0.0' => false,
70
            '5.0.1' => true,
71
        ),
72
        'tidyNode' => array(
73
            '5.0.0' => false,
74
            '5.0.1' => true,
75
        ),
76
77
        'libXMLError' => array(
78
            '5.0' => false,
79
            '5.1' => true,
80
        ),
81
        'PDO' => array(
82
            '5.0' => false,
83
            '5.1' => true,
84
        ),
85
        'PDOStatement' => array(
86
            '5.0' => false,
87
            '5.1' => true,
88
        ),
89
        'AppendIterator' => array(
90
            '5.0' => false,
91
            '5.1' => true,
92
        ),
93
        'EmptyIterator' => array(
94
            '5.0' => false,
95
            '5.1' => true,
96
        ),
97
        'FilterIterator' => array(
98
            '5.0' => false,
99
            '5.1' => true,
100
        ),
101
        'InfiniteIterator' => array(
102
            '5.0' => false,
103
            '5.1' => true,
104
        ),
105
        'IteratorIterator' => array(
106
            '5.0' => false,
107
            '5.1' => true,
108
        ),
109
        'LimitIterator' => array(
110
            '5.0' => false,
111
            '5.1' => true,
112
        ),
113
        'NoRewindIterator' => array(
114
            '5.0' => false,
115
            '5.1' => true,
116
        ),
117
        'ParentIterator' => array(
118
            '5.0' => false,
119
            '5.1' => true,
120
        ),
121
        'RecursiveArrayIterator' => array(
122
            '5.0' => false,
123
            '5.1' => true,
124
        ),
125
        'RecursiveCachingIterator' => array(
126
            '5.0' => false,
127
            '5.1' => true,
128
        ),
129
        'RecursiveFilterIterator' => array(
130
            '5.0' => false,
131
            '5.1' => true,
132
        ),
133
        'SimpleXMLIterator' => array(
134
            '5.0' => false,
135
            '5.1' => true,
136
        ),
137
        'SplFileObject' => array(
138
            '5.0' => false,
139
            '5.1' => true,
140
        ),
141
        'XMLReader' => array(
142
            '5.0' => false,
143
            '5.1' => true,
144
        ),
145
146
        'SplFileInfo' => array(
147
            '5.1.1' => false,
148
            '5.1.2' => true,
149
        ),
150
        'SplTempFileObject' => array(
151
            '5.1.1' => false,
152
            '5.1.2' => true,
153
        ),
154
        'XMLWriter' => array(
155
            '5.1.1' => false,
156
            '5.1.2' => true,
157
        ),
158
159
        'DateTime' => array(
160
            '5.1' => false,
161
            '5.2' => true,
162
        ),
163
        'DateTimeZone' => array(
164
            '5.1' => false,
165
            '5.2' => true,
166
        ),
167
        'RegexIterator' => array(
168
            '5.1' => false,
169
            '5.2' => true,
170
        ),
171
        'RecursiveRegexIterator' => array(
172
            '5.1' => false,
173
            '5.2' => true,
174
        ),
175
        'ReflectionFunctionAbstract' => array(
176
            '5.1' => false,
177
            '5.2' => true,
178
        ),
179
        'ZipArchive' => array(
180
            '5.1' => false,
181
            '5.2' => true,
182
        ),
183
184
        'Closure' => array(
185
            '5.2' => false,
186
            '5.3' => true,
187
        ),
188
        'DateInterval' => array(
189
            '5.2' => false,
190
            '5.3' => true,
191
        ),
192
        'DatePeriod' => array(
193
            '5.2' => false,
194
            '5.3' => true,
195
        ),
196
        'finfo' => array(
197
            '5.2' => false,
198
            '5.3' => true,
199
        ),
200
        'Collator' => array(
201
            '5.2' => false,
202
            '5.3' => true,
203
        ),
204
        'NumberFormatter' => array(
205
            '5.2' => false,
206
            '5.3' => true,
207
        ),
208
        'Locale' => array(
209
            '5.2' => false,
210
            '5.3' => true,
211
        ),
212
        'Normalizer' => array(
213
            '5.2' => false,
214
            '5.3' => true,
215
        ),
216
        'MessageFormatter' => array(
217
            '5.2' => false,
218
            '5.3' => true,
219
        ),
220
        'IntlDateFormatter' => array(
221
            '5.2' => false,
222
            '5.3' => true,
223
        ),
224
        'Phar' => array(
225
            '5.2' => false,
226
            '5.3' => true,
227
        ),
228
        'PharData' => array(
229
            '5.2' => false,
230
            '5.3' => true,
231
        ),
232
        'PharFileInfo' => array(
233
            '5.2' => false,
234
            '5.3' => true,
235
        ),
236
        'FilesystemIterator' => array(
237
            '5.2' => false,
238
            '5.3' => true,
239
        ),
240
        'GlobIterator' => array(
241
            '5.2' => false,
242
            '5.3' => true,
243
        ),
244
        'MultipleIterator' => array(
245
            '5.2' => false,
246
            '5.3' => true,
247
        ),
248
        'RecursiveTreeIterator' => array(
249
            '5.2' => false,
250
            '5.3' => true,
251
        ),
252
        'SplDoublyLinkedList' => array(
253
            '5.2' => false,
254
            '5.3' => true,
255
        ),
256
        'SplFixedArray' => array(
257
            '5.2' => false,
258
            '5.3' => true,
259
        ),
260
        'SplHeap' => array(
261
            '5.2' => false,
262
            '5.3' => true,
263
        ),
264
        'SplMaxHeap' => array(
265
            '5.2' => false,
266
            '5.3' => true,
267
        ),
268
        'SplMinHeap' => array(
269
            '5.2' => false,
270
            '5.3' => true,
271
        ),
272
        'SplObjectStorage' => array(
273
            '5.2' => false,
274
            '5.3' => true,
275
        ),
276
        'SplPriorityQueue' => array(
277
            '5.2' => false,
278
            '5.3' => true,
279
        ),
280
        'SplQueue' => array(
281
            '5.2' => false,
282
            '5.3' => true,
283
        ),
284
        'SplStack' => array(
285
            '5.2' => false,
286
            '5.3' => true,
287
        ),
288
289
        'ResourceBundle' => array(
290
            '5.3.1' => false,
291
            '5.3.2' => true,
292
        ),
293
294
        'CallbackFilterIterator' => array(
295
            '5.3' => false,
296
            '5.4' => true,
297
        ),
298
        'RecursiveCallbackFilterIterator' => array(
299
            '5.3' => false,
300
            '5.4' => true,
301
        ),
302
        'ReflectionZendExtension' => array(
303
            '5.3' => false,
304
            '5.4' => true,
305
        ),
306
        'SessionHandler' => array(
307
            '5.3' => false,
308
            '5.4' => true,
309
        ),
310
        'SNMP' => array(
311
            '5.3' => false,
312
            '5.4' => true,
313
        ),
314
        'Transliterator' => array(
315
            '5.3' => false,
316
            '5.4' => true,
317
        ),
318
        'Spoofchecker' => array(
319
            '5.3' => false,
320
            '5.4' => true,
321
        ),
322
323
        'Generator' => array(
324
            '5.4' => false,
325
            '5.5' => true,
326
        ),
327
        'CURLFile' => array(
328
            '5.4' => false,
329
            '5.5' => true,
330
        ),
331
        'DateTimeImmutable' => array(
332
            '5.4' => false,
333
            '5.5' => true,
334
        ),
335
        'IntlCalendar' => array(
336
            '5.4' => false,
337
            '5.5' => true,
338
        ),
339
        'IntlGregorianCalendar' => array(
340
            '5.4' => false,
341
            '5.5' => true,
342
        ),
343
        'IntlTimeZone' => array(
344
            '5.4' => false,
345
            '5.5' => true,
346
        ),
347
        'IntlBreakIterator' => array(
348
            '5.4' => false,
349
            '5.5' => true,
350
        ),
351
        'IntlRuleBasedBreakIterator' => array(
352
            '5.4' => false,
353
            '5.5' => true,
354
        ),
355
        'IntlCodePointBreakIterator' => array(
356
            '5.4' => false,
357
            '5.5' => true,
358
        ),
359
        'UConverter' => array(
360
            '5.4' => false,
361
            '5.5' => true,
362
        ),
363
364
        'GMP' => array(
365
            '5.5' => false,
366
            '5.6' => true,
367
        ),
368
369
        'IntlChar' => array(
370
            '5.6' => false,
371
            '7.0' => true,
372
        ),
373
        'ReflectionType' => array(
374
            '5.6' => false,
375
            '7.0' => true,
376
        ),
377
        'ReflectionGenerator' => array(
378
            '5.6' => false,
379
            '7.0' => true,
380
        ),
381
382
        'ReflectionClassConstant' => array(
383
            '7.0' => false,
384
            '7.1' => true,
385
        ),
386
387
    );
388
389
    /**
390
     * A list of new Exception classes, not present in older versions.
391
     *
392
     * The array lists : version number with false (not present) or true (present).
393
     * If's sufficient to list the first version where the class appears.
394
     *
395
     * {@internal Classes listed here do not need to be added to the $newClasses
396
     *            property as well.
397
     *            This list is automatically added to the $newClasses property
398
     *            in the `register()` method.}}
399
     *
400
     * @var array(string => array(string => bool))
401
     */
402
    protected $newExceptions = array(
403
        'Exception' => array(
404
            // According to the docs introduced in PHP 5.1, but this appears to be.
405
            // an error.  Class was introduced with try/catch keywords in PHP 5.0.
406
            '4.4' => false,
407
            '5.0' => true,
408
        ),
409
        'ErrorException' => array(
410
            '5.0' => false,
411
            '5.1' => true,
412
        ),
413
        'BadFunctionCallException' => array(
414
            '5.0' => false,
415
            '5.1' => true,
416
        ),
417
        'BadMethodCallException' => array(
418
            '5.0' => false,
419
            '5.1' => true,
420
        ),
421
        'DomainException' => array(
422
            '5.0' => false,
423
            '5.1' => true,
424
        ),
425
        'InvalidArgumentException' => array(
426
            '5.0' => false,
427
            '5.1' => true,
428
        ),
429
        'LengthException' => array(
430
            '5.0' => false,
431
            '5.1' => true,
432
        ),
433
        'LogicException' => array(
434
            '5.0' => false,
435
            '5.1' => true,
436
        ),
437
        'OutOfBoundsException' => array(
438
            '5.0' => false,
439
            '5.1' => true,
440
        ),
441
        'OutOfRangeException' => array(
442
            '5.0' => false,
443
            '5.1' => true,
444
        ),
445
        'OverflowException' => array(
446
            '5.0' => false,
447
            '5.1' => true,
448
        ),
449
        'RangeException' => array(
450
            '5.0' => false,
451
            '5.1' => true,
452
        ),
453
        'RuntimeException' => array(
454
            '5.0' => false,
455
            '5.1' => true,
456
        ),
457
        'UnderflowException' => array(
458
            '5.0' => false,
459
            '5.1' => true,
460
        ),
461
        'UnexpectedValueException' => array(
462
            '5.0' => false,
463
            '5.1' => true,
464
        ),
465
        'DOMException' => array(
466
            '4.4' => false,
467
            '5.0' => true,
468
        ),
469
        'mysqli_sql_exception' => array(
470
            '4.4' => false,
471
            '5.0' => true,
472
        ),
473
        'PDOException' => array(
474
            '5.0' => false,
475
            '5.1' => true,
476
        ),
477
        'ReflectionException' => array(
478
            '4.4' => false,
479
            '5.0' => true,
480
        ),
481
        'SoapFault' => array(
482
            '4.4' => false,
483
            '5.0' => true,
484
        ),
485
486
        'PharException' => array(
487
            '5.2' => false,
488
            '5.3' => true,
489
        ),
490
491
        'SNMPException' => array(
492
            '5.3' => false,
493
            '5.4' => true,
494
        ),
495
496
        'IntlException' => array(
497
            '5.5.0' => false,
498
            '5.5.1' => true,
499
        ),
500
501
        'Error' => array(
502
            '5.6' => false,
503
            '7.0' => true,
504
        ),
505
        'ArithmeticError' => array(
506
            '5.6' => false,
507
            '7.0' => true,
508
        ),
509
        'AssertionError' => array(
510
            '5.6' => false,
511
            '7.0' => true,
512
        ),
513
        'DivisionByZeroError' => array(
514
            '5.6' => false,
515
            '7.0' => true,
516
        ),
517
        'ParseError' => array(
518
            '5.6' => false,
519
            '7.0' => true,
520
        ),
521
        'TypeError' => array(
522
            '5.6' => false,
523
            '7.0' => true,
524
        ),
525
        'UI\Exception\InvalidArgumentException' => array(
526
            '5.6' => false,
527
            '7.0' => true,
528
        ),
529
        'UI\Exception\RuntimeException' => array(
530
            '5.6' => false,
531
            '7.0' => true,
532
        ),
533
534
        'ArgumentCountError' => array(
535
            '7.0' => false,
536
            '7.1' => true,
537
        ),
538
    );
539
540
541
    /**
542
     * Returns an array of tokens this test wants to listen for.
543
     *
544
     * @return array
545
     */
546
    public function register()
547
    {
548
        // Handle case-insensitivity of class names.
549
        $this->newClasses = $this->arrayKeysToLowercase($this->newClasses);
550
        $this->newExceptions = $this->arrayKeysToLowercase($this->newExceptions);
551
552
        // Add the Exception classes to the Classes list.
553
        $this->newClasses = array_merge($this->newClasses, $this->newExceptions);
554
555
        $targets = array(
556
            T_NEW,
557
            T_CLASS,
558
            T_DOUBLE_COLON,
559
            T_FUNCTION,
560
            T_CLOSURE,
561
            T_CATCH,
562
        );
563
564
        if (defined('T_ANON_CLASS')) {
565
            $targets[] = constant('T_ANON_CLASS');
566
        }
567
568
        return $targets;
569
570
    }//end register()
571
572
573
    /**
574
     * Processes this test, when one of its tokens is encountered.
575
     *
576
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
577
     * @param int                   $stackPtr  The position of the current token in
578
     *                                         the stack passed in $tokens.
579
     *
580
     * @return void
581
     */
582
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
0 ignored issues
show
Bug introduced by
The type PHP_CodeSniffer_File was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
583
    {
584
        $tokens = $phpcsFile->getTokens();
585
586
        switch ($tokens[$stackPtr]['type']) {
587
            case 'T_FUNCTION':
588
            case 'T_CLOSURE':
589
                $this->processFunctionToken($phpcsFile, $stackPtr);
590
                break;
591
592
            case 'T_CATCH':
593
                $this->processCatchToken($phpcsFile, $stackPtr);
594
                break;
595
596
            default:
597
                $this->processSingularToken($phpcsFile, $stackPtr);
598
                break;
599
        }
600
601
    }//end process()
602
603
604
    /**
605
     * Processes this test for when a token resulting in a singular class name is encountered.
606
     *
607
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
608
     * @param int                   $stackPtr  The position of the current token in
609
     *                                         the stack passed in $tokens.
610
     *
611
     * @return void
612
     */
613
    private function processSingularToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
614
    {
615
        $tokens      = $phpcsFile->getTokens();
616
        $FQClassName = '';
617
618
        if ($tokens[$stackPtr]['type'] === 'T_NEW') {
619
            $FQClassName = $this->getFQClassNameFromNewToken($phpcsFile, $stackPtr);
620
621
        } elseif ($tokens[$stackPtr]['type'] === 'T_CLASS' || $tokens[$stackPtr]['type'] === 'T_ANON_CLASS') {
622
            $FQClassName = $this->getFQExtendedClassName($phpcsFile, $stackPtr);
623
624
        } elseif ($tokens[$stackPtr]['type'] === 'T_DOUBLE_COLON') {
625
            $FQClassName = $this->getFQClassNameFromDoubleColonToken($phpcsFile, $stackPtr);
626
        }
627
628
        if ($FQClassName === '') {
629
            return;
630
        }
631
632
        $className   = substr($FQClassName, 1); // Remove global namespace indicator.
633
        $classNameLc = strtolower($className);
634
635
        if (isset($this->newClasses[$classNameLc]) === false) {
636
            return;
637
        }
638
639
        $itemInfo = array(
640
            'name'   => $className,
641
            'nameLc' => $classNameLc,
642
        );
643
        $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
644
645
    }//end processSingularToken()
646
647
648
    /**
649
     * Processes this test for when a function token is encountered.
650
     *
651
     * - Detect new classes when used as a type hint.
652
     *
653
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
654
     * @param int                   $stackPtr  The position of the current token in
655
     *                                         the stack passed in $tokens.
656
     *
657
     * @return void
658
     */
659
    private function processFunctionToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
660
    {
661
        // Retrieve typehints stripped of global NS indicator and/or nullable indicator.
662
        $typeHints = $this->getTypeHintsFromFunctionDeclaration($phpcsFile, $stackPtr);
663
        if (empty($typeHints) || is_array($typeHints) === false) {
664
            return;
665
        }
666
667
        foreach ($typeHints as $hint) {
668
669
            $typeHintLc = strtolower($hint);
670
671
            if (isset($this->newClasses[$typeHintLc]) === true) {
672
                $itemInfo = array(
673
                    'name'   => $hint,
674
                    'nameLc' => $typeHintLc,
675
                );
676
                $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
677
            }
678
        }
679
    }
680
681
682
    /**
683
     * Processes this test for when a catch token is encountered.
684
     *
685
     * - Detect exceptions when used in a catch statement.
686
     *
687
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
688
     * @param int                   $stackPtr  The position of the current token in
689
     *                                         the stack passed in $tokens.
690
     *
691
     * @return void
692
     */
693
    private function processCatchToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
694
    {
695
        $tokens = $phpcsFile->getTokens();
696
697
        // Bow out during live coding.
698
        if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
699
            return;
700
        }
701
702
        $opener = $tokens[$stackPtr]['parenthesis_opener'];
703
        $closer = ($tokens[$stackPtr]['parenthesis_closer'] + 1);
704
        $name   = '';
705
        $listen = array(
706
            // Parts of a (namespaced) class name.
707
            T_STRING              => true,
708
            T_NS_SEPARATOR        => true,
709
            // End/split tokens.
710
            T_VARIABLE            => false,
711
            T_BITWISE_OR          => false,
712
            T_CLOSE_CURLY_BRACKET => false, // Shouldn't be needed as we expect a var before this.
713
        );
714
715
        for ($i = ($opener + 1); $i < $closer; $i++) {
716
            if (isset($listen[$tokens[$i]['code']]) === false) {
717
                continue;
718
            }
719
720
            if ($listen[$tokens[$i]['code']] === true) {
721
                $name .= $tokens[$i]['content'];
722
                continue;
723
            } else {
724
                if (empty($name) === true) {
725
                    // Weird, we should have a name by the time we encounter a variable or |.
726
                    // So this may be the closer.
727
                    continue;
728
                }
729
730
                $name   = ltrim($name, '\\');
731
                $nameLC = strtolower($name);
732
733
                if (isset($this->newExceptions[$nameLC]) === true) {
734
                    $itemInfo = array(
735
                        'name'   => $name,
736
                        'nameLc' => $nameLC,
737
                    );
738
                    $this->handleFeature($phpcsFile, $i, $itemInfo);
739
                }
740
741
                // Reset for a potential multi-catch.
742
                $name = '';
743
            }
744
        }
745
    }
746
747
748
    /**
749
     * Get the relevant sub-array for a specific item from a multi-dimensional array.
750
     *
751
     * @param array $itemInfo Base information about the item.
752
     *
753
     * @return array Version and other information about the item.
754
     */
755
    public function getItemArray(array $itemInfo)
756
    {
757
        return $this->newClasses[$itemInfo['nameLc']];
758
    }
759
760
761
    /**
762
     * Get the error message template for this sniff.
763
     *
764
     * @return string
765
     */
766
    protected function getErrorMsgTemplate()
767
    {
768
        return 'The built-in class '.parent::getErrorMsgTemplate();
769
    }
770
771
772
}//end class
773