Completed
Push — master ( 75c610...6d4259 )
by Wim
11s
created

NewClassesSniff::processSingularToken()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 19
nc 12
nop 2
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 View Code Duplication
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
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 View Code Duplication
    private function processFunctionToken(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
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...
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