• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 17343936854

30 Aug 2025 12:45PM UTC coverage: 79.184% (+0.6%) from 78.578%
17343936854

Pull #1206

github

web-flow
Merge e70599f05 into ca606d9f6
Pull Request #1206: Tokenizer/PHP: improved tokenization of fully qualified exit/die/true/false/null

60 of 63 new or added lines in 5 files covered. (95.24%)

1 existing line in 1 file now uncovered.

25346 of 32009 relevant lines covered (79.18%)

74.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.63
/src/Tokenizers/PHP.php
1
<?php
2
/**
3
 * Tokenizes PHP code.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Tokenizers;
11

12
use PHP_CodeSniffer\Util\Common;
13
use PHP_CodeSniffer\Util\Tokens;
14

15
class PHP extends Tokenizer
16
{
17

18
    /**
19
     * A list of tokens that are allowed to open a scope.
20
     *
21
     * This array also contains information about what kind of token the scope
22
     * opener uses to open and close the scope, if the token strictly requires
23
     * an opener, if the token can share a scope closer, and who it can be shared
24
     * with. An example of a token that shares a scope closer is a CASE scope.
25
     *
26
     * @var array
27
     */
28
    public $scopeOpeners = [
29
        T_IF            => [
30
            'start'  => [
31
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
32
                T_COLON              => T_COLON,
33
            ],
34
            'end'    => [
35
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
36
                T_ENDIF               => T_ENDIF,
37
                T_ELSE                => T_ELSE,
38
                T_ELSEIF              => T_ELSEIF,
39
            ],
40
            'strict' => false,
41
            'shared' => false,
42
            'with'   => [
43
                T_ELSE   => T_ELSE,
44
                T_ELSEIF => T_ELSEIF,
45
            ],
46
        ],
47
        T_TRY           => [
48
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
49
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
50
            'strict' => true,
51
            'shared' => false,
52
            'with'   => [],
53
        ],
54
        T_CATCH         => [
55
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
56
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
57
            'strict' => true,
58
            'shared' => false,
59
            'with'   => [],
60
        ],
61
        T_FINALLY       => [
62
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
63
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
64
            'strict' => true,
65
            'shared' => false,
66
            'with'   => [],
67
        ],
68
        T_ELSE          => [
69
            'start'  => [
70
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
71
                T_COLON              => T_COLON,
72
            ],
73
            'end'    => [
74
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
75
                T_ENDIF               => T_ENDIF,
76
            ],
77
            'strict' => false,
78
            'shared' => false,
79
            'with'   => [
80
                T_IF     => T_IF,
81
                T_ELSEIF => T_ELSEIF,
82
            ],
83
        ],
84
        T_ELSEIF        => [
85
            'start'  => [
86
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
87
                T_COLON              => T_COLON,
88
            ],
89
            'end'    => [
90
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
91
                T_ENDIF               => T_ENDIF,
92
                T_ELSE                => T_ELSE,
93
                T_ELSEIF              => T_ELSEIF,
94
            ],
95
            'strict' => false,
96
            'shared' => false,
97
            'with'   => [
98
                T_IF   => T_IF,
99
                T_ELSE => T_ELSE,
100
            ],
101
        ],
102
        T_FOR           => [
103
            'start'  => [
104
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
105
                T_COLON              => T_COLON,
106
            ],
107
            'end'    => [
108
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
109
                T_ENDFOR              => T_ENDFOR,
110
            ],
111
            'strict' => false,
112
            'shared' => false,
113
            'with'   => [],
114
        ],
115
        T_FOREACH       => [
116
            'start'  => [
117
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
118
                T_COLON              => T_COLON,
119
            ],
120
            'end'    => [
121
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
122
                T_ENDFOREACH          => T_ENDFOREACH,
123
            ],
124
            'strict' => false,
125
            'shared' => false,
126
            'with'   => [],
127
        ],
128
        T_INTERFACE     => [
129
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
130
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
131
            'strict' => true,
132
            'shared' => false,
133
            'with'   => [],
134
        ],
135
        T_FUNCTION      => [
136
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
137
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
138
            'strict' => true,
139
            'shared' => false,
140
            'with'   => [],
141
        ],
142
        T_CLASS         => [
143
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
144
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
145
            'strict' => true,
146
            'shared' => false,
147
            'with'   => [],
148
        ],
149
        T_TRAIT         => [
150
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
151
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
152
            'strict' => true,
153
            'shared' => false,
154
            'with'   => [],
155
        ],
156
        T_ENUM          => [
157
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
158
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
159
            'strict' => true,
160
            'shared' => false,
161
            'with'   => [],
162
        ],
163
        T_USE           => [
164
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
165
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
166
            'strict' => false,
167
            'shared' => false,
168
            'with'   => [],
169
        ],
170
        T_DECLARE       => [
171
            'start'  => [
172
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
173
                T_COLON              => T_COLON,
174
            ],
175
            'end'    => [
176
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
177
                T_ENDDECLARE          => T_ENDDECLARE,
178
            ],
179
            'strict' => false,
180
            'shared' => false,
181
            'with'   => [],
182
        ],
183
        T_NAMESPACE     => [
184
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
185
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
186
            'strict' => false,
187
            'shared' => false,
188
            'with'   => [],
189
        ],
190
        T_WHILE         => [
191
            'start'  => [
192
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
193
                T_COLON              => T_COLON,
194
            ],
195
            'end'    => [
196
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
197
                T_ENDWHILE            => T_ENDWHILE,
198
            ],
199
            'strict' => false,
200
            'shared' => false,
201
            'with'   => [],
202
        ],
203
        T_DO            => [
204
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
205
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
206
            'strict' => true,
207
            'shared' => false,
208
            'with'   => [],
209
        ],
210
        T_SWITCH        => [
211
            'start'  => [
212
                T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
213
                T_COLON              => T_COLON,
214
            ],
215
            'end'    => [
216
                T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
217
                T_ENDSWITCH           => T_ENDSWITCH,
218
            ],
219
            'strict' => true,
220
            'shared' => false,
221
            'with'   => [],
222
        ],
223
        T_CASE          => [
224
            'start'  => [
225
                T_COLON     => T_COLON,
226
                T_SEMICOLON => T_SEMICOLON,
227
            ],
228
            'end'    => [
229
                T_BREAK    => T_BREAK,
230
                T_RETURN   => T_RETURN,
231
                T_CONTINUE => T_CONTINUE,
232
                T_THROW    => T_THROW,
233
                T_EXIT     => T_EXIT,
234
                T_GOTO     => T_GOTO,
235
            ],
236
            'strict' => true,
237
            'shared' => true,
238
            'with'   => [
239
                T_DEFAULT => T_DEFAULT,
240
                T_CASE    => T_CASE,
241
                T_SWITCH  => T_SWITCH,
242
            ],
243
        ],
244
        T_DEFAULT       => [
245
            'start'  => [
246
                T_COLON     => T_COLON,
247
                T_SEMICOLON => T_SEMICOLON,
248
            ],
249
            'end'    => [
250
                T_BREAK    => T_BREAK,
251
                T_RETURN   => T_RETURN,
252
                T_CONTINUE => T_CONTINUE,
253
                T_THROW    => T_THROW,
254
                T_EXIT     => T_EXIT,
255
                T_GOTO     => T_GOTO,
256
            ],
257
            'strict' => true,
258
            'shared' => true,
259
            'with'   => [
260
                T_CASE   => T_CASE,
261
                T_SWITCH => T_SWITCH,
262
            ],
263
        ],
264
        T_MATCH         => [
265
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
266
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
267
            'strict' => true,
268
            'shared' => false,
269
            'with'   => [],
270
        ],
271
        T_START_HEREDOC => [
272
            'start'  => [T_START_HEREDOC => T_START_HEREDOC],
273
            'end'    => [T_END_HEREDOC => T_END_HEREDOC],
274
            'strict' => true,
275
            'shared' => false,
276
            'with'   => [],
277
        ],
278
        T_START_NOWDOC  => [
279
            'start'  => [T_START_NOWDOC => T_START_NOWDOC],
280
            'end'    => [T_END_NOWDOC => T_END_NOWDOC],
281
            'strict' => true,
282
            'shared' => false,
283
            'with'   => [],
284
        ],
285
    ];
286

287
    /**
288
     * A list of tokens that end the scope.
289
     *
290
     * This array is just a unique collection of the end tokens
291
     * from the scopeOpeners array. The data is duplicated here to
292
     * save time during parsing of the file.
293
     *
294
     * @var array<int|string, int|string>
295
     */
296
    public $endScopeTokens = [
297
        T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
298
        T_ENDIF               => T_ENDIF,
299
        T_ENDFOR              => T_ENDFOR,
300
        T_ENDFOREACH          => T_ENDFOREACH,
301
        T_ENDWHILE            => T_ENDWHILE,
302
        T_ENDSWITCH           => T_ENDSWITCH,
303
        T_ENDDECLARE          => T_ENDDECLARE,
304
        T_BREAK               => T_BREAK,
305
        T_END_HEREDOC         => T_END_HEREDOC,
306
        T_END_NOWDOC          => T_END_NOWDOC,
307
    ];
308

309
    /**
310
     * Known lengths of tokens.
311
     *
312
     * @var array<string|int, int>
313
     */
314
    public $knownLengths = [
315
        T_ABSTRACT                 => 8,
316
        T_AND_EQUAL                => 2,
317
        T_ARRAY                    => 5,
318
        T_AS                       => 2,
319
        T_BOOLEAN_AND              => 2,
320
        T_BOOLEAN_OR               => 2,
321
        T_BREAK                    => 5,
322
        T_CALLABLE                 => 8,
323
        T_CASE                     => 4,
324
        T_CATCH                    => 5,
325
        T_CLASS                    => 5,
326
        T_CLASS_C                  => 9,
327
        T_CLONE                    => 5,
328
        T_CONCAT_EQUAL             => 2,
329
        T_CONST                    => 5,
330
        T_CONTINUE                 => 8,
331
        T_CURLY_OPEN               => 2,
332
        T_DEC                      => 2,
333
        T_DECLARE                  => 7,
334
        T_DEFAULT                  => 7,
335
        T_DIR                      => 7,
336
        T_DIV_EQUAL                => 2,
337
        T_DO                       => 2,
338
        T_DOLLAR_OPEN_CURLY_BRACES => 2,
339
        T_DOUBLE_ARROW             => 2,
340
        T_DOUBLE_COLON             => 2,
341
        T_ECHO                     => 4,
342
        T_ELLIPSIS                 => 3,
343
        T_ELSE                     => 4,
344
        T_ELSEIF                   => 6,
345
        T_EMPTY                    => 5,
346
        T_ENDDECLARE               => 10,
347
        T_ENDFOR                   => 6,
348
        T_ENDFOREACH               => 10,
349
        T_ENDIF                    => 5,
350
        T_ENDSWITCH                => 9,
351
        T_ENDWHILE                 => 8,
352
        T_ENUM                     => 4,
353
        T_ENUM_CASE                => 4,
354
        T_EVAL                     => 4,
355
        T_EXTENDS                  => 7,
356
        T_FILE                     => 8,
357
        T_FINAL                    => 5,
358
        T_FINALLY                  => 7,
359
        T_FN                       => 2,
360
        T_FOR                      => 3,
361
        T_FOREACH                  => 7,
362
        T_FUNCTION                 => 8,
363
        T_FUNC_C                   => 12,
364
        T_GLOBAL                   => 6,
365
        T_GOTO                     => 4,
366
        T_HALT_COMPILER            => 15,
367
        T_IF                       => 2,
368
        T_IMPLEMENTS               => 10,
369
        T_INC                      => 2,
370
        T_INCLUDE                  => 7,
371
        T_INCLUDE_ONCE             => 12,
372
        T_INSTANCEOF               => 10,
373
        T_INSTEADOF                => 9,
374
        T_INTERFACE                => 9,
375
        T_ISSET                    => 5,
376
        T_IS_EQUAL                 => 2,
377
        T_IS_GREATER_OR_EQUAL      => 2,
378
        T_IS_IDENTICAL             => 3,
379
        T_IS_NOT_EQUAL             => 2,
380
        T_IS_NOT_IDENTICAL         => 3,
381
        T_IS_SMALLER_OR_EQUAL      => 2,
382
        T_LINE                     => 8,
383
        T_LIST                     => 4,
384
        T_LOGICAL_AND              => 3,
385
        T_LOGICAL_OR               => 2,
386
        T_LOGICAL_XOR              => 3,
387
        T_MATCH                    => 5,
388
        T_MATCH_ARROW              => 2,
389
        T_MATCH_DEFAULT            => 7,
390
        T_METHOD_C                 => 10,
391
        T_MINUS_EQUAL              => 2,
392
        T_POW_EQUAL                => 3,
393
        T_MOD_EQUAL                => 2,
394
        T_MUL_EQUAL                => 2,
395
        T_NAMESPACE                => 9,
396
        T_NS_C                     => 13,
397
        T_NS_SEPARATOR             => 1,
398
        T_NEW                      => 3,
399
        T_NULLSAFE_OBJECT_OPERATOR => 3,
400
        T_OBJECT_OPERATOR          => 2,
401
        T_OPEN_TAG_WITH_ECHO       => 3,
402
        T_OR_EQUAL                 => 2,
403
        T_PLUS_EQUAL               => 2,
404
        T_PRINT                    => 5,
405
        T_PRIVATE                  => 7,
406
        T_PRIVATE_SET              => 12,
407
        T_PUBLIC                   => 6,
408
        T_PUBLIC_SET               => 11,
409
        T_PROTECTED                => 9,
410
        T_PROTECTED_SET            => 14,
411
        T_READONLY                 => 8,
412
        T_REQUIRE                  => 7,
413
        T_REQUIRE_ONCE             => 12,
414
        T_RETURN                   => 6,
415
        T_STATIC                   => 6,
416
        T_SWITCH                   => 6,
417
        T_THROW                    => 5,
418
        T_TRAIT                    => 5,
419
        T_TRAIT_C                  => 9,
420
        T_TRY                      => 3,
421
        T_UNSET                    => 5,
422
        T_USE                      => 3,
423
        T_VAR                      => 3,
424
        T_WHILE                    => 5,
425
        T_XOR_EQUAL                => 2,
426
        T_YIELD                    => 5,
427
        T_OPEN_CURLY_BRACKET       => 1,
428
        T_CLOSE_CURLY_BRACKET      => 1,
429
        T_OPEN_SQUARE_BRACKET      => 1,
430
        T_CLOSE_SQUARE_BRACKET     => 1,
431
        T_OPEN_PARENTHESIS         => 1,
432
        T_CLOSE_PARENTHESIS        => 1,
433
        T_COLON                    => 1,
434
        T_STRING_CONCAT            => 1,
435
        T_INLINE_THEN              => 1,
436
        T_INLINE_ELSE              => 1,
437
        T_NULLABLE                 => 1,
438
        T_NULL                     => 4,
439
        T_FALSE                    => 5,
440
        T_TRUE                     => 4,
441
        T_SEMICOLON                => 1,
442
        T_EQUAL                    => 1,
443
        T_MULTIPLY                 => 1,
444
        T_DIVIDE                   => 1,
445
        T_PLUS                     => 1,
446
        T_MINUS                    => 1,
447
        T_MODULUS                  => 1,
448
        T_POW                      => 2,
449
        T_SPACESHIP                => 3,
450
        T_COALESCE                 => 2,
451
        T_COALESCE_EQUAL           => 3,
452
        T_BITWISE_AND              => 1,
453
        T_BITWISE_OR               => 1,
454
        T_BITWISE_XOR              => 1,
455
        T_SL                       => 2,
456
        T_SR                       => 2,
457
        T_SL_EQUAL                 => 3,
458
        T_SR_EQUAL                 => 3,
459
        T_GREATER_THAN             => 1,
460
        T_LESS_THAN                => 1,
461
        T_BOOLEAN_NOT              => 1,
462
        T_SELF                     => 4,
463
        T_PARENT                   => 6,
464
        T_COMMA                    => 1,
465
        T_THIS                     => 4,
466
        T_CLOSURE                  => 8,
467
        T_BACKTICK                 => 1,
468
        T_OPEN_SHORT_ARRAY         => 1,
469
        T_CLOSE_SHORT_ARRAY        => 1,
470
        T_TYPE_UNION               => 1,
471
        T_TYPE_INTERSECTION        => 1,
472
        T_TYPE_OPEN_PARENTHESIS    => 1,
473
        T_TYPE_CLOSE_PARENTHESIS   => 1,
474
    ];
475

476
    /**
477
     * Contexts in which keywords should always be tokenized as T_STRING.
478
     *
479
     * @var array
480
     */
481
    protected $tstringContexts = [
482
        T_OBJECT_OPERATOR          => true,
483
        T_NULLSAFE_OBJECT_OPERATOR => true,
484
        T_FUNCTION                 => true,
485
        T_CLASS                    => true,
486
        T_INTERFACE                => true,
487
        T_TRAIT                    => true,
488
        T_ENUM                     => true,
489
        T_ENUM_CASE                => true,
490
        T_EXTENDS                  => true,
491
        T_IMPLEMENTS               => true,
492
        T_ATTRIBUTE                => true,
493
        T_NEW                      => true,
494
        T_CONST                    => true,
495
        T_NS_SEPARATOR             => true,
496
        T_USE                      => true,
497
        T_NAMESPACE                => true,
498
        T_PAAMAYIM_NEKUDOTAYIM     => true,
499
    ];
500

501
    /**
502
     * A cache of different token types, resolved into arrays.
503
     *
504
     * @var array
505
     * @see standardiseToken()
506
     */
507
    private static $resolveTokenCache = [];
508

509

510
    /**
511
     * Creates an array of tokens when given some PHP code.
512
     *
513
     * Starts by using token_get_all() but does a lot of extra processing
514
     * to insert information about the context of the token.
515
     *
516
     * @param string $string The string to tokenize.
517
     *
518
     * @return array
519
     */
520
    protected function tokenize($string)
3,205✔
521
    {
522
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,205✔
523
            echo "\t*** START PHP TOKENIZING ***".PHP_EOL;
×
524
            $isWin = false;
×
525
            if (stripos(PHP_OS, 'WIN') === 0) {
×
526
                $isWin = true;
×
527
            }
528
        }
529

530
        $tokens      = @token_get_all($string);
3,205✔
531
        $finalTokens = [];
3,205✔
532

533
        $newStackPtr       = 0;
3,205✔
534
        $numTokens         = count($tokens);
3,205✔
535
        $lastNotEmptyToken = 0;
3,205✔
536

537
        $insideInlineIf         = [];
3,205✔
538
        $insideUseGroup         = false;
3,205✔
539
        $insideConstDeclaration = false;
3,205✔
540

541
        $commentTokenizer = new Comment();
3,205✔
542

543
        for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
3,205✔
544
            // Special case for tokens we have needed to blank out.
545
            if ($tokens[$stackPtr] === null) {
3,205✔
546
                continue;
603✔
547
            }
548

549
            $token        = (array) $tokens[$stackPtr];
3,205✔
550
            $tokenIsArray = isset($token[1]);
3,205✔
551

552
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,205✔
553
                if ($tokenIsArray === true) {
×
554
                    $type    = Tokens::tokenName($token[0]);
×
555
                    $content = Common::prepareForOutput($token[1]);
×
556
                } else {
557
                    $newToken = self::resolveSimpleToken($token[0]);
×
558
                    $type     = $newToken['type'];
×
559
                    $content  = Common::prepareForOutput($token[0]);
×
560
                }
561

562
                echo "\tProcess token ";
×
563
                if ($tokenIsArray === true) {
×
564
                    echo "[$stackPtr]";
×
565
                } else {
566
                    echo " $stackPtr ";
×
567
                }
568

569
                echo ": $type => $content";
×
570
            }//end if
571

572
            if ($newStackPtr > 0
2,162✔
573
                && isset(Tokens::$emptyTokens[$finalTokens[($newStackPtr - 1)]['code']]) === false
3,205✔
574
            ) {
1,043✔
575
                $lastNotEmptyToken = ($newStackPtr - 1);
3,205✔
576
            }
1,043✔
577

578
            /*
579
                If we are using \r\n newline characters, the \r and \n are sometimes
580
                split over two tokens. This normally occurs after comments. We need
581
                to merge these two characters together so that our line endings are
582
                consistent for all lines.
583
            */
584

585
            if ($tokenIsArray === true && substr($token[1], -1) === "\r") {
3,205✔
586
                if (isset($tokens[($stackPtr + 1)]) === true
×
587
                    && is_array($tokens[($stackPtr + 1)]) === true
×
588
                    && $tokens[($stackPtr + 1)][1][0] === "\n"
×
589
                ) {
590
                    $token[1] .= "\n";
×
591
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
592
                        if ($isWin === true) {
×
593
                            echo '\n';
×
594
                        } else {
595
                            echo "\033[30;1m\\n\033[0m";
×
596
                        }
597
                    }
598

599
                    if ($tokens[($stackPtr + 1)][1] === "\n") {
×
600
                        // This token's content has been merged into the previous,
601
                        // so we can skip it.
602
                        $tokens[($stackPtr + 1)] = '';
×
603
                    } else {
604
                        $tokens[($stackPtr + 1)][1] = substr($tokens[($stackPtr + 1)][1], 1);
×
605
                    }
606
                }
607
            }//end if
608

609
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,205✔
610
                echo PHP_EOL;
×
611
            }
612

613
            /*
614
                Before PHP 5.5, the yield keyword was tokenized as
615
                T_STRING. So look for and change this token in
616
                earlier versions.
617
            */
618

619
            if (PHP_VERSION_ID < 50500
3,205✔
620
                && $tokenIsArray === true
3,205✔
621
                && $token[0] === T_STRING
3,205✔
622
                && strtolower($token[1]) === 'yield'
3,205✔
623
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,205✔
624
            ) {
1,043✔
625
                // Could still be a context sensitive keyword or "yield from" and potentially multi-line,
626
                // so adjust the token stack in place.
627
                $token[0] = T_YIELD;
427✔
628

629
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
427✔
630
                    echo "\t\t* token $stackPtr changed from T_STRING to T_YIELD".PHP_EOL;
×
631
                }
632
            }
427✔
633

634
            /*
635
                Tokenize context sensitive keyword as string when it should be string.
636
            */
637

638
            if ($tokenIsArray === true
2,162✔
639
                && isset(Tokens::$contextSensitiveKeywords[$token[0]]) === true
3,205✔
640
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
3,105✔
641
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
3,005✔
642
                || $insideConstDeclaration === true)
3,105✔
643
            ) {
1,043✔
644
                if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,579✔
645
                    $preserveKeyword = false;
2,559✔
646

647
                    // `new class`, and `new static` should be preserved.
648
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,559✔
649
                        && ($token[0] === T_CLASS
2,261✔
650
                        || $token[0] === T_STATIC)
2,287✔
651
                    ) {
816✔
652
                        $preserveKeyword = true;
1,507✔
653
                    }
477✔
654

655
                    // `new readonly class` should be preserved.
656
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,559✔
657
                        && strtolower($token[1]) === 'readonly'
2,559✔
658
                    ) {
816✔
659
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
175✔
660
                            if (is_array($tokens[$i]) === false
175✔
661
                                || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
175✔
662
                            ) {
663
                                break;
175✔
664
                            }
665
                        }
666

667
                        if (is_array($tokens[$i]) === true && $tokens[$i][0] === T_CLASS) {
175✔
668
                            $preserveKeyword = true;
175✔
669
                        }
670
                    }
671

672
                    // `new class extends` `new class implements` should be preserved
673
                    if (($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS)
2,559✔
674
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CLASS
2,559✔
675
                    ) {
816✔
676
                        $preserveKeyword = true;
525✔
677
                    }
175✔
678

679
                    // `namespace\` should be preserved
680
                    if ($token[0] === T_NAMESPACE) {
2,559✔
681
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
747✔
682
                            if (is_array($tokens[$i]) === false) {
747✔
683
                                break;
525✔
684
                            }
685

686
                            if (isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true) {
747✔
687
                                continue;
525✔
688
                            }
689

690
                            if ($tokens[$i][0] === T_NS_SEPARATOR) {
572✔
691
                                $preserveKeyword = true;
572✔
692
                            }
267✔
693

694
                            break;
572✔
695
                        }
696
                    }
267✔
697

698
                    // Fully Qualified `\exit`/`\die` should be preserved.
699
                    if ($token[0] === T_EXIT
2,559✔
700
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
2,559✔
701
                    ) {
816✔
702
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
980✔
703
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
980✔
704
                                continue;
980✔
705
                            }
706

707
                            if ($finalTokens[$i]['code'] !== T_STRING
980✔
708
                                && $finalTokens[$i]['code'] !== T_NAMESPACE
980✔
709
                            ) {
490✔
710
                                $preserveKeyword = true;
980✔
711
                            }
490✔
712

713
                            break;
980✔
714
                        }
715
                    }
490✔
716
                }//end if
816✔
717

718
                // Types in typed constants should not be touched, but the constant name should be.
719
                if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,579✔
720
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,579✔
721
                    || $insideConstDeclaration === true
1,743✔
722
                ) {
836✔
723
                    $preserveKeyword = true;
2,357✔
724

725
                    // Find the next non-empty token.
726
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,357✔
727
                        if (is_array($tokens[$i]) === true
2,357✔
728
                            && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,357✔
729
                        ) {
736✔
730
                            continue;
1,709✔
731
                        }
732

733
                        break;
2,357✔
734
                    }
735

736
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
2,357✔
737
                        $preserveKeyword        = false;
859✔
738
                        $insideConstDeclaration = false;
859✔
739
                    }
262✔
740
                }//end if
736✔
741

742
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,579✔
743
                    $preserveKeyword = true;
673✔
744

745
                    for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
673✔
746
                        if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
673✔
747
                            continue;
585✔
748
                        }
749

750
                        if ($finalTokens[$i]['code'] === T_FUNCTION) {
673✔
751
                            $preserveKeyword = false;
585✔
752
                        }
195✔
753

754
                        break;
673✔
755
                    }
756
                }
220✔
757

758
                if ($preserveKeyword === false) {
2,579✔
759
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,160✔
760
                        $type = Tokens::tokenName($token[0]);
×
761
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
762
                    }
763

764
                    $finalTokens[$newStackPtr] = [
1,160✔
765
                        'code'    => T_STRING,
1,160✔
766
                        'type'    => 'T_STRING',
1,160✔
767
                        'content' => $token[1],
1,160✔
768
                    ];
418✔
769

770
                    $newStackPtr++;
1,160✔
771
                    continue;
1,160✔
772
                }
773
            }//end if
761✔
774

775
            /*
776
                Mark the start of a constant declaration to allow for handling keyword to T_STRING
777
                convertion for constant names using reserved keywords.
778
            */
779

780
            if ($tokenIsArray === true && $token[0] === T_CONST) {
3,205✔
781
                $insideConstDeclaration = true;
2,617✔
782
            }
847✔
783

784
            /*
785
                Close an open "inside constant declaration" marker when no keyword conversion was needed.
786
            */
787

788
            if ($insideConstDeclaration === true
2,162✔
789
                && $tokenIsArray === false
3,205✔
790
                && ($token[0] === '=' || $token[0] === ';')
3,205✔
791
            ) {
1,043✔
792
                $insideConstDeclaration = false;
1,199✔
793
            }
458✔
794

795
            /*
796
                Special case for `static` used as a function name, i.e. `static()`.
797

798
                Note: this may incorrectly change the static keyword directly before a DNF property type.
799
                If so, this will be caught and corrected for in the additional processing.
800
            */
801

802
            if ($tokenIsArray === true
2,162✔
803
                && $token[0] === T_STATIC
3,205✔
804
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
3,205✔
805
            ) {
1,043✔
806
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,056✔
807
                    if (is_array($tokens[$i]) === true
2,056✔
808
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,056✔
809
                    ) {
660✔
810
                        continue;
1,408✔
811
                    }
812

813
                    if ($tokens[$i][0] === '(') {
2,056✔
814
                        $finalTokens[$newStackPtr] = [
525✔
815
                            'code'    => T_STRING,
525✔
816
                            'type'    => 'T_STRING',
525✔
817
                            'content' => $token[1],
525✔
818
                        ];
175✔
819

820
                        $newStackPtr++;
525✔
821
                        continue 2;
525✔
822
                    }
823

824
                    break;
2,056✔
825
                }
826
            }//end if
660✔
827

828
            /*
829
                Prior to PHP 7.4, PHP didn't support stand-alone PHP open tags at the end of a file
830
                (without a new line), so we need to make sure that the tokenization in PHPCS is consistent
831
                cross-version PHP by retokenizing to T_OPEN_TAG.
832
            */
833

834
            if (PHP_VERSION_ID < 70400
3,205✔
835
                && $tokenIsArray === true
3,205✔
836
                // PHP < 7.4 with short open tags off.
837
                && (($stackPtr === ($numTokens - 1)
3,205✔
838
                && $token[0] === T_INLINE_HTML
3,205✔
839
                && stripos($token[1], '<?php') === 0)
2,124✔
840
                // PHP < 7.4 with short open tags on.
1,081✔
841
                || ($stackPtr === ($numTokens - 2)
3,205✔
842
                && $token[0] === T_OPEN_TAG
3,205✔
843
                && $token[1] === '<?'
3,205✔
844
                && is_array($tokens[($stackPtr + 1)]) === true
3,205✔
845
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,205✔
846
                && strtolower($tokens[($stackPtr + 1)][1]) === 'php'))
3,205✔
847
            ) {
1,043✔
848
                if ($token[0] === T_INLINE_HTML) {
4✔
849
                    $finalTokens[$newStackPtr] = [
2✔
850
                        'code'    => T_OPEN_TAG,
2✔
851
                        'type'    => 'T_OPEN_TAG',
2✔
852
                        'content' => $token[1],
2✔
853
                    ];
854
                } else {
2✔
855
                    $finalTokens[$newStackPtr] = [
2✔
856
                        'code'    => T_OPEN_TAG,
2✔
857
                        'type'    => 'T_OPEN_TAG',
2✔
858
                        'content' => $token[1].$tokens[($stackPtr + 1)][1],
2✔
859
                    ];
860

861
                    $stackPtr++;
2✔
862
                }
863

864
                $newStackPtr++;
4✔
865
                continue;
4✔
866
            }//end if
867

868
            /*
869
                Parse doc blocks into something that can be easily iterated over.
870
            */
871

872
            if ($tokenIsArray === true
2,162✔
873
                && ($token[0] === T_DOC_COMMENT
3,205✔
874
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
3,205✔
875
            ) {
1,043✔
876
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
93✔
877
                foreach ($commentTokens as $commentToken) {
93✔
878
                    $finalTokens[$newStackPtr] = $commentToken;
93✔
879
                    $newStackPtr++;
93✔
880
                }
31✔
881

882
                continue;
93✔
883
            }
884

885
            /*
886
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
887
            */
888

889
            if (PHP_VERSION_ID >= 80000
3,205✔
890
                && $tokenIsArray === true
3,205✔
891
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
3,205✔
892
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
893
                && is_array($tokens[($stackPtr + 1)]) === true
3,205✔
894
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,205✔
895
            ) {
1,043✔
896
                $nextToken = $tokens[($stackPtr + 1)];
842✔
897

898
                // If the next token is a single new line, merge it into the comment token
899
                // and set to it up to be skipped.
900
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
842✔
901
                    $token[1] .= $nextToken[1];
603✔
902
                    $tokens[($stackPtr + 1)] = null;
603✔
903

904
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
603✔
905
                        echo "\t\t* merged newline after comment into comment token $stackPtr".PHP_EOL;
×
906
                    }
907
                } else {
908
                    // This may be a whitespace token consisting of multiple new lines.
909
                    if (strpos($nextToken[1], "\r\n") === 0) {
634✔
910
                        $token[1] .= "\r\n";
15✔
911
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
15✔
912
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
619✔
913
                        $token[1] .= "\n\r";
×
914
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
915
                    } else if (strpos($nextToken[1], "\n") === 0) {
619✔
916
                        $token[1] .= "\n";
619✔
917
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
619✔
918
                    }
919

920
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
634✔
921
                        echo "\t\t* stripped first newline after comment and added it to comment token $stackPtr".PHP_EOL;
×
922
                    }
923
                }//end if
924
            }//end if
925

926
            /*
927
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
928
                T_LNUMBER and T_STRING token values into a single token value, and
929
                then ignore the T_STRING token.
930
            */
931

932
            if (PHP_VERSION_ID < 80100
3,205✔
933
                && $tokenIsArray === true && $token[1] === '0'
3,205✔
934
                && (isset($tokens[($stackPtr + 1)]) === true
2,691✔
935
                && is_array($tokens[($stackPtr + 1)]) === true
2,177✔
936
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,177✔
937
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,177✔
938
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,177✔
939
                && $tokens[($stackPtr + 1)][1][1] !== '_')
2,691✔
940
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
3,205✔
941
            ) {
1,043✔
942
                $finalTokens[$newStackPtr] = [
74✔
943
                    'code'    => T_LNUMBER,
74✔
944
                    'type'    => 'T_LNUMBER',
74✔
945
                    'content' => $token[1] .= $matches[1],
74✔
946
                ];
947
                $newStackPtr++;
74✔
948

949
                if (isset($matches[2]) === true && $matches[2] !== '') {
74✔
950
                    $type = 'T_LNUMBER';
20✔
951
                    if ($matches[2][0] === '_') {
20✔
952
                        $type = 'T_STRING';
20✔
953
                    }
10✔
954

955
                    $finalTokens[$newStackPtr] = [
20✔
956
                        'code'    => constant($type),
20✔
957
                        'type'    => $type,
20✔
958
                        'content' => $matches[2],
20✔
959
                    ];
960
                    $newStackPtr++;
20✔
961
                }
10✔
962

963
                $stackPtr++;
74✔
964
                continue;
74✔
965
            }//end if
966

967
            /*
968
                PHP 8.1 introduced two dedicated tokens for the & character.
969
                Retokenizing both of these to T_BITWISE_AND, which is the
970
                token PHPCS already tokenized them as.
971
            */
972

973
            if ($tokenIsArray === true
2,162✔
974
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
3,205✔
975
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
3,205✔
976
            ) {
1,043✔
977
                $finalTokens[$newStackPtr] = [
443✔
978
                    'code'    => T_BITWISE_AND,
443✔
979
                    'type'    => 'T_BITWISE_AND',
443✔
980
                    'content' => $token[1],
443✔
981
                ];
443✔
982
                $newStackPtr++;
443✔
983
                continue;
443✔
984
            }
985

986
            /*
987
                If this is a double quoted string, PHP will tokenize the whole
988
                thing which causes problems with the scope map when braces are
989
                within the string. So we need to merge the tokens together to
990
                provide a single string.
991
            */
992

993
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
3,205✔
994
                // Binary casts need a special token.
995
                if ($token[0] === 'b"') {
105✔
996
                    $finalTokens[$newStackPtr] = [
×
997
                        'code'    => T_BINARY_CAST,
×
998
                        'type'    => 'T_BINARY_CAST',
×
999
                        'content' => 'b',
×
1000
                    ];
1001
                    $newStackPtr++;
×
1002
                }
1003

1004
                $tokenContent = '"';
105✔
1005
                $nestedVars   = [];
105✔
1006
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
105✔
1007
                    $subToken        = (array) $tokens[$i];
105✔
1008
                    $subTokenIsArray = isset($subToken[1]);
105✔
1009

1010
                    if ($subTokenIsArray === true) {
105✔
1011
                        $tokenContent .= $subToken[1];
105✔
1012
                        if (($subToken[1] === '{'
105✔
1013
                            || $subToken[1] === '${')
105✔
1014
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
105✔
1015
                        ) {
35✔
1016
                            $nestedVars[] = $i;
75✔
1017
                        }
20✔
1018
                    } else {
35✔
1019
                        $tokenContent .= $subToken[0];
105✔
1020
                        if ($subToken[0] === '}') {
105✔
1021
                            array_pop($nestedVars);
60✔
1022
                        }
20✔
1023
                    }
1024

1025
                    if ($subTokenIsArray === false
70✔
1026
                        && $subToken[0] === '"'
105✔
1027
                        && empty($nestedVars) === true
105✔
1028
                    ) {
35✔
1029
                        // We found the other end of the double quoted string.
1030
                        break;
105✔
1031
                    }
1032
                }//end for
35✔
1033

1034
                $stackPtr = $i;
105✔
1035

1036
                // Convert each line within the double quoted string to a
1037
                // new token, so it conforms with other multiple line tokens.
1038
                $tokenLines = explode($this->eolChar, $tokenContent);
105✔
1039
                $numLines   = count($tokenLines);
105✔
1040
                $newToken   = [];
105✔
1041

1042
                for ($j = 0; $j < $numLines; $j++) {
105✔
1043
                    $newToken['content'] = $tokenLines[$j];
105✔
1044
                    if ($j === ($numLines - 1)) {
105✔
1045
                        if ($tokenLines[$j] === '') {
105✔
1046
                            break;
75✔
1047
                        }
1048
                    } else {
35✔
1049
                        $newToken['content'] .= $this->eolChar;
60✔
1050
                    }
1051

1052
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
105✔
1053
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
105✔
1054
                    $finalTokens[$newStackPtr] = $newToken;
105✔
1055
                    $newStackPtr++;
105✔
1056
                }
35✔
1057

1058
                // Continue, as we're done with this token.
1059
                continue;
105✔
1060
            }//end if
1061

1062
            /*
1063
                Detect binary casting and assign the casts their own token.
1064
            */
1065

1066
            if ($tokenIsArray === true
2,162✔
1067
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
3,205✔
1068
                && (substr($token[1], 0, 2) === 'b"'
3,093✔
1069
                || substr($token[1], 0, 2) === "b'")
3,093✔
1070
            ) {
1,043✔
1071
                $finalTokens[$newStackPtr] = [
×
1072
                    'code'    => T_BINARY_CAST,
×
1073
                    'type'    => 'T_BINARY_CAST',
×
1074
                    'content' => 'b',
×
1075
                ];
1076
                $newStackPtr++;
×
1077
                $token[1] = substr($token[1], 1);
×
1078
            }
1079

1080
            if ($tokenIsArray === true
2,162✔
1081
                && $token[0] === T_STRING_CAST
3,205✔
1082
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
3,205✔
1083
            ) {
1,043✔
1084
                $finalTokens[$newStackPtr] = [
×
1085
                    'code'    => T_BINARY_CAST,
×
1086
                    'type'    => 'T_BINARY_CAST',
×
1087
                    'content' => $token[1],
×
1088
                ];
1089
                $newStackPtr++;
×
1090
                continue;
×
1091
            }
1092

1093
            /*
1094
                If this is a heredoc, PHP will tokenize the whole
1095
                thing which causes problems when heredocs don't
1096
                contain real PHP code, which is almost never.
1097
                We want to leave the start and end heredoc tokens
1098
                alone though.
1099
            */
1100

1101
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
3,205✔
1102
                // Add the start heredoc token to the final array.
1103
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
135✔
1104

1105
                // Check if this is actually a nowdoc and use a different token
1106
                // to help the sniffs.
1107
                $nowdoc = false;
135✔
1108
                if (strpos($token[1], "'") !== false) {
135✔
1109
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
18✔
1110
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
18✔
1111
                    $nowdoc = true;
18✔
1112
                }
6✔
1113

1114
                $tokenContent = '';
135✔
1115
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
135✔
1116
                    $subTokenIsArray = is_array($tokens[$i]);
135✔
1117
                    if ($subTokenIsArray === true
90✔
1118
                        && $tokens[$i][0] === T_END_HEREDOC
135✔
1119
                    ) {
45✔
1120
                        // We found the other end of the heredoc.
1121
                        break;
132✔
1122
                    }
1123

1124
                    if ($subTokenIsArray === true) {
135✔
1125
                        $tokenContent .= $tokens[$i][1];
135✔
1126
                    } else {
45✔
1127
                        $tokenContent .= $tokens[$i];
114✔
1128
                    }
1129
                }
45✔
1130

1131
                if ($i === $numTokens) {
135✔
1132
                    // We got to the end of the file and never
1133
                    // found the closing token, so this probably wasn't
1134
                    // a heredoc.
1135
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
1136
                        $type = $finalTokens[$newStackPtr]['type'];
×
1137
                        echo "\t\t* failed to find the end of the here/nowdoc".PHP_EOL;
×
1138
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
1139
                    }
1140

1141
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
3✔
1142
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
3✔
1143
                    $newStackPtr++;
3✔
1144
                    continue;
3✔
1145
                }
1146

1147
                $stackPtr = $i;
132✔
1148
                $newStackPtr++;
132✔
1149

1150
                // Convert each line within the heredoc to a
1151
                // new token, so it conforms with other multiple line tokens.
1152
                $tokenLines = explode($this->eolChar, $tokenContent);
132✔
1153
                $numLines   = count($tokenLines);
132✔
1154
                $newToken   = [];
132✔
1155

1156
                for ($j = 0; $j < $numLines; $j++) {
132✔
1157
                    $newToken['content'] = $tokenLines[$j];
132✔
1158
                    if ($j === ($numLines - 1)) {
132✔
1159
                        if ($tokenLines[$j] === '') {
132✔
1160
                            break;
132✔
1161
                        }
1162
                    } else {
1163
                        $newToken['content'] .= $this->eolChar;
132✔
1164
                    }
1165

1166
                    if ($nowdoc === true) {
132✔
1167
                        $newToken['code'] = T_NOWDOC;
18✔
1168
                        $newToken['type'] = 'T_NOWDOC';
18✔
1169
                    } else {
6✔
1170
                        $newToken['code'] = T_HEREDOC;
132✔
1171
                        $newToken['type'] = 'T_HEREDOC';
132✔
1172
                    }
1173

1174
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1175
                    $newStackPtr++;
132✔
1176
                }//end for
44✔
1177

1178
                // Add the end heredoc token to the final array.
1179
                $finalTokens[$newStackPtr] = self::standardiseToken($tokens[$stackPtr]);
132✔
1180

1181
                if ($nowdoc === true) {
132✔
1182
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
18✔
1183
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
18✔
1184
                }
6✔
1185

1186
                $newStackPtr++;
132✔
1187

1188
                // Continue, as we're done with this token.
1189
                continue;
132✔
1190
            }//end if
1191

1192
            /*
1193
                Enum keyword for PHP < 8.1
1194
            */
1195

1196
            if ($tokenIsArray === true
2,162✔
1197
                && $token[0] === T_STRING
3,205✔
1198
                && strtolower($token[1]) === 'enum'
3,205✔
1199
            ) {
1,043✔
1200
                // Get the next non-empty token.
1201
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,423✔
1202
                    if (is_array($tokens[$i]) === false
1,423✔
1203
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,423✔
1204
                    ) {
475✔
1205
                        break;
1,423✔
1206
                    }
1207
                }
259✔
1208

1209
                if (isset($tokens[$i]) === true
1,423✔
1210
                    && is_array($tokens[$i]) === true
1,423✔
1211
                    && $tokens[$i][0] === T_STRING
1,423✔
1212
                ) {
475✔
1213
                    // Modify $tokens directly so we can use it later when converting enum "case".
1214
                    $tokens[$stackPtr][0] = T_ENUM;
556✔
1215

1216
                    $newToken            = [];
556✔
1217
                    $newToken['code']    = T_ENUM;
556✔
1218
                    $newToken['type']    = 'T_ENUM';
556✔
1219
                    $newToken['content'] = $token[1];
556✔
1220
                    $finalTokens[$newStackPtr] = $newToken;
556✔
1221

1222
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
556✔
1223
                        echo "\t\t* token $stackPtr changed from T_STRING to T_ENUM".PHP_EOL;
×
1224
                    }
1225

1226
                    $newStackPtr++;
556✔
1227
                    continue;
556✔
1228
                }
1229
            }//end if
435✔
1230

1231
            /*
1232
                Convert enum "case" to T_ENUM_CASE
1233
            */
1234

1235
            if ($tokenIsArray === true
2,162✔
1236
                && $token[0] === T_CASE
3,205✔
1237
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,205✔
1238
            ) {
1,043✔
1239
                $isEnumCase = false;
1,581✔
1240
                $scope      = 1;
1,581✔
1241

1242
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,581✔
1243
                    if ($tokens[$i] === '}') {
1,581✔
1244
                        $scope++;
1,536✔
1245
                        continue;
1,536✔
1246
                    }
1247

1248
                    if ($tokens[$i] === '{') {
1,581✔
1249
                        $scope--;
1,581✔
1250
                        continue;
1,581✔
1251
                    }
1252

1253
                    if (is_array($tokens[$i]) === false) {
1,581✔
1254
                        continue;
1,581✔
1255
                    }
1256

1257
                    if ($scope !== 0) {
1,581✔
1258
                        continue;
1,581✔
1259
                    }
1260

1261
                    if ($tokens[$i][0] === T_SWITCH) {
1,056✔
1262
                        break;
990✔
1263
                    }
1264

1265
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,056✔
1266
                        $isEnumCase = true;
132✔
1267
                        break;
132✔
1268
                    }
1269
                }//end for
352✔
1270

1271
                if ($isEnumCase === true) {
1,581✔
1272
                    // Modify $tokens directly so we can use it as optimisation for other enum "case".
1273
                    $tokens[$stackPtr][0] = T_ENUM_CASE;
132✔
1274

1275
                    $newToken            = [];
132✔
1276
                    $newToken['code']    = T_ENUM_CASE;
132✔
1277
                    $newToken['type']    = 'T_ENUM_CASE';
132✔
1278
                    $newToken['content'] = $token[1];
132✔
1279
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1280

1281
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
132✔
1282
                        echo "\t\t* token $stackPtr changed from T_CASE to T_ENUM_CASE".PHP_EOL;
×
1283
                    }
1284

1285
                    $newStackPtr++;
132✔
1286
                    continue;
132✔
1287
                }
1288
            }//end if
505✔
1289

1290
            /*
1291
                Asymmetric visibility for PHP < 8.4
1292
            */
1293

1294
            if ($tokenIsArray === true
2,162✔
1295
                && in_array($token[0], [T_PUBLIC, T_PROTECTED, T_PRIVATE], true) === true
3,205✔
1296
                && ($stackPtr + 3) < $numTokens
3,205✔
1297
                && $tokens[($stackPtr + 1)] === '('
3,205✔
1298
                && is_array($tokens[($stackPtr + 2)]) === true
3,205✔
1299
                && $tokens[($stackPtr + 2)][0] === T_STRING
3,205✔
1300
                && strtolower($tokens[($stackPtr + 2)][1]) === 'set'
3,205✔
1301
                && $tokens[($stackPtr + 3)] === ')'
3,205✔
1302
            ) {
1,043✔
1303
                $newToken = [];
362✔
1304
                if ($token[0] === T_PUBLIC) {
362✔
1305
                    $oldCode          = 'T_PUBLIC';
350✔
1306
                    $newToken['code'] = T_PUBLIC_SET;
350✔
1307
                    $newToken['type'] = 'T_PUBLIC_SET';
350✔
1308
                } else if ($token[0] === T_PROTECTED) {
362✔
1309
                    $oldCode          = 'T_PROTECTED';
362✔
1310
                    $newToken['code'] = T_PROTECTED_SET;
362✔
1311
                    $newToken['type'] = 'T_PROTECTED_SET';
362✔
1312
                } else {
181✔
1313
                    $oldCode          = 'T_PRIVATE';
362✔
1314
                    $newToken['code'] = T_PRIVATE_SET;
362✔
1315
                    $newToken['type'] = 'T_PRIVATE_SET';
362✔
1316
                }
1317

1318
                $newToken['content']       = $token[1].'('.$tokens[($stackPtr + 2)][1].')';
362✔
1319
                $finalTokens[$newStackPtr] = $newToken;
362✔
1320
                $newStackPtr++;
362✔
1321

1322
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
362✔
1323
                    $newCode = $newToken['type'];
×
1324
                    echo "\t\t* tokens from $stackPtr changed from $oldCode to $newCode".PHP_EOL;
×
1325
                }
1326

1327
                // We processed an extra 3 tokens, for `(`, `set`, and `)`.
1328
                $stackPtr += 3;
362✔
1329

1330
                continue;
362✔
1331
            }//end if
1332

1333
            /*
1334
                As of PHP 8.0 fully qualified, partially qualified and namespace relative
1335
                identifier names are tokenized differently.
1336
                This "undoes" the new tokenization so the tokenization will be the same in
1337
                in PHP 5, 7 and 8.
1338
            */
1339

1340
            if (PHP_VERSION_ID >= 80000
3,205✔
1341
                && $tokenIsArray === true
3,205✔
1342
                && ($token[0] === T_NAME_QUALIFIED
2,124✔
1343
                || $token[0] === T_NAME_FULLY_QUALIFIED
1,081✔
1344
                || $token[0] === T_NAME_RELATIVE)
2,162✔
1345
            ) {
1,043✔
1346
                $name = $token[1];
850✔
1347

1348
                if ($token[0] === T_NAME_FULLY_QUALIFIED) {
850✔
1349
                    $newToken            = [];
752✔
1350
                    $newToken['code']    = T_NS_SEPARATOR;
752✔
1351
                    $newToken['type']    = 'T_NS_SEPARATOR';
752✔
1352
                    $newToken['content'] = '\\';
752✔
1353
                    $finalTokens[$newStackPtr] = $newToken;
752✔
1354
                    ++$newStackPtr;
752✔
1355

1356
                    $name = ltrim($name, '\\');
752✔
1357
                }
1358

1359
                if ($token[0] === T_NAME_RELATIVE) {
850✔
1360
                    $newToken            = [];
620✔
1361
                    $newToken['code']    = T_NAMESPACE;
620✔
1362
                    $newToken['type']    = 'T_NAMESPACE';
620✔
1363
                    $newToken['content'] = substr($name, 0, 9);
620✔
1364
                    $finalTokens[$newStackPtr] = $newToken;
620✔
1365
                    ++$newStackPtr;
620✔
1366

1367
                    $newToken            = [];
620✔
1368
                    $newToken['code']    = T_NS_SEPARATOR;
620✔
1369
                    $newToken['type']    = 'T_NS_SEPARATOR';
620✔
1370
                    $newToken['content'] = '\\';
620✔
1371
                    $finalTokens[$newStackPtr] = $newToken;
620✔
1372
                    ++$newStackPtr;
620✔
1373

1374
                    $name = substr($name, 10);
620✔
1375
                }
1376

1377
                // Special case keywords which can be used in fully qualified form.
1378
                if ($token[0] === T_NAME_FULLY_QUALIFIED) {
850✔
1379
                    $specialCasedType = null;
752✔
1380
                    $nameLc           = strtolower($name);
752✔
1381
                    if ($nameLc === 'exit' || $nameLc === 'die') {
752✔
1382
                        $specialCasedType = 'T_EXIT';
490✔
1383
                    } else if ($nameLc === 'true') {
545✔
1384
                        $specialCasedType = 'T_TRUE';
233✔
1385
                    } else if ($nameLc === 'false') {
545✔
1386
                        $specialCasedType = 'T_FALSE';
233✔
1387
                    } else if ($nameLc === 'null') {
545✔
1388
                        $specialCasedType = 'T_NULL';
233✔
1389
                    }
1390

1391
                    if ($specialCasedType !== null) {
752✔
1392
                        $newToken            = [];
656✔
1393
                        $newToken['code']    = constant($specialCasedType);
656✔
1394
                        $newToken['type']    = $specialCasedType;
656✔
1395
                        $newToken['content'] = $name;
656✔
1396
                        $finalTokens[$newStackPtr] = $newToken;
656✔
1397
                        ++$newStackPtr;
656✔
1398

1399
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
656✔
NEW
1400
                            $type    = Tokens::tokenName($token[0]);
×
NEW
1401
                            $content = Common::prepareForOutput($token[1]);
×
NEW
1402
                            echo "\t\t* token $stackPtr split into individual tokens T_NS_SEPARATOR + $specialCasedType".PHP_EOL;
×
1403
                        }
1404

1405
                        continue;
656✔
1406
                    }
1407
                }//end if
1408

1409
                $parts     = explode('\\', $name);
684✔
1410
                $partCount = count($parts);
684✔
1411
                $lastPart  = ($partCount - 1);
684✔
1412

1413
                foreach ($parts as $i => $part) {
684✔
1414
                    $newToken            = [];
684✔
1415
                    $newToken['code']    = T_STRING;
684✔
1416
                    $newToken['type']    = 'T_STRING';
684✔
1417
                    $newToken['content'] = $part;
684✔
1418
                    $finalTokens[$newStackPtr] = $newToken;
684✔
1419
                    ++$newStackPtr;
684✔
1420

1421
                    if ($i !== $lastPart) {
684✔
1422
                        $newToken            = [];
509✔
1423
                        $newToken['code']    = T_NS_SEPARATOR;
509✔
1424
                        $newToken['type']    = 'T_NS_SEPARATOR';
509✔
1425
                        $newToken['content'] = '\\';
509✔
1426
                        $finalTokens[$newStackPtr] = $newToken;
509✔
1427
                        ++$newStackPtr;
509✔
1428
                    }
1429
                }
1430

1431
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
684✔
1432
                    $type    = Tokens::tokenName($token[0]);
×
1433
                    $content = Common::prepareForOutput($token[1]);
×
1434
                    echo "\t\t* token $stackPtr split into individual tokens; was: $type => $content".PHP_EOL;
×
1435
                }
1436

1437
                continue;
684✔
1438
            }//end if
1439

1440
            /*
1441
                PHP 8.0 Attributes
1442
            */
1443

1444
            if (PHP_VERSION_ID < 80000
3,205✔
1445
                && $token[0] === T_COMMENT
3,205✔
1446
                && strpos($token[1], '#[') === 0
3,205✔
1447
            ) {
1,043✔
1448
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
34✔
1449
                if ($subTokens !== null) {
34✔
1450
                    array_splice($tokens, $stackPtr, 1, $subTokens);
34✔
1451
                    $numTokens = count($tokens);
34✔
1452

1453
                    $tokenIsArray = true;
34✔
1454
                    $token        = $tokens[$stackPtr];
34✔
1455
                } else {
17✔
1456
                    $token[0] = T_ATTRIBUTE;
34✔
1457
                }
1458
            }
17✔
1459

1460
            if ($tokenIsArray === true
2,162✔
1461
                && $token[0] === T_ATTRIBUTE
3,205✔
1462
            ) {
1,043✔
1463
                // Go looking for the close bracket.
1464
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1465

1466
                $newToken            = [];
51✔
1467
                $newToken['code']    = T_ATTRIBUTE;
51✔
1468
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1469
                $newToken['content'] = '#[';
51✔
1470
                $finalTokens[$newStackPtr] = $newToken;
51✔
1471

1472
                $tokens[$bracketCloser]    = [];
51✔
1473
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1474
                $tokens[$bracketCloser][1] = ']';
51✔
1475

1476
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1477
                    echo "\t\t* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END".PHP_EOL;
×
1478
                }
1479

1480
                $newStackPtr++;
51✔
1481
                continue;
51✔
1482
            }//end if
1483

1484
            /*
1485
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1486
                token and ensures that the colon after it is always T_COLON.
1487
            */
1488

1489
            if ($tokenIsArray === true
2,162✔
1490
                && ($token[0] === T_STRING
3,205✔
1491
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
3,205✔
1492
            ) {
1,043✔
1493
                // Get the next non-empty token.
1494
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
3,019✔
1495
                    if (is_array($tokens[$i]) === false
3,019✔
1496
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
3,019✔
1497
                    ) {
981✔
1498
                        break;
3,019✔
1499
                    }
1500
                }
943✔
1501

1502
                if (isset($tokens[$i]) === true
3,019✔
1503
                    && is_array($tokens[$i]) === false
3,019✔
1504
                    && $tokens[$i] === ':'
3,019✔
1505
                ) {
981✔
1506
                    // Get the previous non-empty token.
1507
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,749✔
1508
                        if (is_array($tokens[$j]) === false
1,749✔
1509
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
1,749✔
1510
                        ) {
583✔
1511
                            break;
1,749✔
1512
                        }
1513
                    }
544✔
1514

1515
                    if (is_array($tokens[$j]) === false
1,749✔
1516
                        && ($tokens[$j] === '('
1,698✔
1517
                        || $tokens[$j] === ',')
1,698✔
1518
                    ) {
583✔
1519
                        $newToken            = [];
795✔
1520
                        $newToken['code']    = T_PARAM_NAME;
795✔
1521
                        $newToken['type']    = 'T_PARAM_NAME';
795✔
1522
                        $newToken['content'] = $token[1];
795✔
1523
                        $finalTokens[$newStackPtr] = $newToken;
795✔
1524

1525
                        $newStackPtr++;
795✔
1526

1527
                        // Modify the original token stack so that future checks, like
1528
                        // determining T_COLON vs T_INLINE_ELSE can handle this correctly.
1529
                        $tokens[$stackPtr][0] = T_PARAM_NAME;
795✔
1530

1531
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
795✔
1532
                            $type = Tokens::tokenName($token[0]);
×
1533
                            echo "\t\t* token $stackPtr changed from $type to T_PARAM_NAME".PHP_EOL;
×
1534
                        }
1535

1536
                        continue;
795✔
1537
                    }
1538
                }//end if
534✔
1539
            }//end if
981✔
1540

1541
            /*
1542
                "readonly" keyword for PHP < 8.1
1543
            */
1544

1545
            if ($tokenIsArray === true
2,162✔
1546
                && strtolower($token[1]) === 'readonly'
3,205✔
1547
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,471✔
1548
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
2,509✔
1549
            ) {
1,043✔
1550
                // Get the next non-whitespace token.
1551
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,041✔
1552
                    if (is_array($tokens[$i]) === false
1,041✔
1553
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,041✔
1554
                    ) {
347✔
1555
                        break;
1,041✔
1556
                    }
1557
                }
347✔
1558

1559
                $isReadonlyKeyword = false;
1,041✔
1560

1561
                if (isset($tokens[$i]) === false
1,041✔
1562
                    || $tokens[$i] !== '('
1,041✔
1563
                ) {
347✔
1564
                    $isReadonlyKeyword = true;
1,041✔
1565
                } else if ($tokens[$i] === '(') {
347✔
1566
                    /*
1567
                     * Skip over tokens which can be used in type declarations.
1568
                     * At this point, the only token types which need to be taken into consideration
1569
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1570
                     * and the union/intersection/dnf parentheses.
1571
                     */
1572

1573
                    $foundDNFParens = 1;
×
1574
                    $foundDNFPipe   = 0;
×
1575

1576
                    for (++$i; $i < $numTokens; $i++) {
×
1577
                        if (is_array($tokens[$i]) === true) {
×
1578
                            $tokenType = $tokens[$i][0];
×
1579
                        } else {
1580
                            $tokenType = $tokens[$i];
×
1581
                        }
1582

1583
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1584
                            continue;
×
1585
                        }
1586

1587
                        if ($tokenType === '|') {
×
1588
                            ++$foundDNFPipe;
×
1589
                            continue;
×
1590
                        }
1591

1592
                        if ($tokenType === ')') {
×
1593
                            ++$foundDNFParens;
×
1594
                            continue;
×
1595
                        }
1596

1597
                        if ($tokenType === '(') {
×
1598
                            ++$foundDNFParens;
×
1599
                            continue;
×
1600
                        }
1601

1602
                        if ($tokenType === T_STRING
1603
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1604
                            || $tokenType === T_NAME_RELATIVE
×
1605
                            || $tokenType === T_NAME_QUALIFIED
×
1606
                            || $tokenType === T_ARRAY
×
1607
                            || $tokenType === T_NAMESPACE
×
1608
                            || $tokenType === T_NS_SEPARATOR
×
1609
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1610
                            || $tokenType === '&' // PHP < 8.0.
×
1611
                        ) {
1612
                            continue;
×
1613
                        }
1614

1615
                        // Reached the next token after.
1616
                        if (($foundDNFParens % 2) === 0
×
1617
                            && $foundDNFPipe >= 1
×
1618
                            && ($tokenType === T_VARIABLE
×
1619
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1620
                        ) {
1621
                            $isReadonlyKeyword = true;
×
1622
                        }
1623

1624
                        break;
×
1625
                    }//end for
1626
                }//end if
1627

1628
                if ($isReadonlyKeyword === true) {
1,041✔
1629
                    $finalTokens[$newStackPtr] = [
1,041✔
1630
                        'code'    => T_READONLY,
1,041✔
1631
                        'type'    => 'T_READONLY',
1,041✔
1632
                        'content' => $token[1],
1,041✔
1633
                    ];
347✔
1634
                    $newStackPtr++;
1,041✔
1635

1636
                    // Also modify the original token stack so that
1637
                    // future checks (like looking for T_NULLABLE) can
1638
                    // detect the T_READONLY token more easily.
1639
                    $tokens[$stackPtr][0] = T_READONLY;
1,041✔
1640
                    $token[0] = T_READONLY;
1,041✔
1641

1642
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
1,041✔
1643
                        echo "\t\t* token $stackPtr changed from $type to T_READONLY".PHP_EOL;
347✔
1644
                    }
1645
                } else {
347✔
1646
                    $finalTokens[$newStackPtr] = [
×
1647
                        'code'    => T_STRING,
×
1648
                        'type'    => 'T_STRING',
×
1649
                        'content' => $token[1],
×
1650
                    ];
1651
                    $newStackPtr++;
×
1652

1653
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1654
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
1655
                    }
1656
                }//end if
1657

1658
                continue;
1,041✔
1659
            }//end if
1660

1661
            /*
1662
                Before PHP 7.0, "yield from" was tokenized as
1663
                T_YIELD, T_WHITESPACE and T_STRING. So look for
1664
                and change this token in earlier versions.
1665
            */
1666

1667
            if (PHP_VERSION_ID < 70000
3,205✔
1668
                && $tokenIsArray === true
3,205✔
1669
                && $token[0] === T_YIELD
3,205✔
1670
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1671
                && isset($tokens[($stackPtr + 2)]) === true
3,205✔
1672
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,205✔
1673
                && strpos($tokens[($stackPtr + 1)][1], $this->eolChar) === false
3,205✔
1674
                && $tokens[($stackPtr + 2)][0] === T_STRING
3,205✔
1675
                && strtolower($tokens[($stackPtr + 2)][1]) === 'from'
3,205✔
1676
            ) {
1,043✔
1677
                // Single-line "yield from" with only whitespace between.
1678
                $finalTokens[$newStackPtr] = [
195✔
1679
                    'code'    => T_YIELD_FROM,
195✔
1680
                    'type'    => 'T_YIELD_FROM',
195✔
1681
                    'content' => $token[1].$tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1],
195✔
1682
                ];
1683

1684
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
195✔
1685
                    for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) {
×
1686
                        $type    = Tokens::tokenName($tokens[$i][0]);
×
1687
                        $content = Common::prepareForOutput($tokens[$i][1]);
×
1688
                        echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL;
×
1689
                    }
1690
                }
1691

1692
                $newStackPtr++;
195✔
1693
                $stackPtr += 2;
195✔
1694

1695
                continue;
195✔
1696
            } else if (PHP_VERSION_ID < 80300
3,205✔
1697
                && $tokenIsArray === true
3,205✔
1698
                && $token[0] === T_STRING
3,205✔
1699
                && strtolower($token[1]) === 'from'
3,205✔
1700
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
3,205✔
1701
            ) {
1,043✔
1702
                /*
1703
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1704
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1705
                    We want to keep the tokenization of the tokens between, but need to change the
1706
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1707
                */
1708

1709
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
40✔
1710
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
40✔
1711

1712
                $finalTokens[$newStackPtr] = [
40✔
1713
                    'code'    => T_YIELD_FROM,
40✔
1714
                    'type'    => 'T_YIELD_FROM',
40✔
1715
                    'content' => $token[1],
40✔
1716
                ];
1717
                $newStackPtr++;
40✔
1718

1719
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
40✔
1720
                    echo "\t\t* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD".PHP_EOL;
×
1721
                    echo "\t\t* token $stackPtr changed into T_YIELD_FROM; was: T_STRING".PHP_EOL;
×
1722
                }
1723

1724
                continue;
40✔
1725
            } else if (PHP_VERSION_ID >= 70000
3,205✔
1726
                && $tokenIsArray === true
3,205✔
1727
                && $token[0] === T_YIELD_FROM
3,205✔
1728
                && strpos($token[1], $this->eolChar) !== false
3,205✔
1729
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
3,205✔
1730
            ) {
1,043✔
1731
                /*
1732
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1733
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1734
                    separately for consistency.
1735
                */
1736

1737
                $finalTokens[$newStackPtr] = [
40✔
1738
                    'code'    => T_YIELD_FROM,
40✔
1739
                    'type'    => 'T_YIELD_FROM',
40✔
1740
                    'content' => substr($token[1], 0, 5),
40✔
1741
                ];
20✔
1742
                $newStackPtr++;
40✔
1743

1744
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
40✔
1745
                $numLines   = count($tokenLines);
40✔
1746
                $newToken   = [
20✔
1747
                    'type'    => 'T_WHITESPACE',
40✔
1748
                    'code'    => T_WHITESPACE,
40✔
1749
                    'content' => '',
40✔
1750
                ];
20✔
1751

1752
                foreach ($tokenLines as $i => $line) {
40✔
1753
                    $newToken['content'] = $line;
40✔
1754
                    if ($i === ($numLines - 1)) {
40✔
1755
                        if ($line === '') {
40✔
1756
                            break;
20✔
1757
                        }
1758
                    } else {
1759
                        $newToken['content'] .= $this->eolChar;
40✔
1760
                    }
1761

1762
                    $finalTokens[$newStackPtr] = $newToken;
40✔
1763
                    $newStackPtr++;
40✔
1764
                }
1765

1766
                $finalTokens[$newStackPtr] = [
40✔
1767
                    'code'    => T_YIELD_FROM,
40✔
1768
                    'type'    => 'T_YIELD_FROM',
40✔
1769
                    'content' => substr($token[1], -4),
40✔
1770
                ];
20✔
1771
                $newStackPtr++;
40✔
1772

1773
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
40✔
1774
                    echo "\t\t* token $stackPtr split into 'yield', one or more whitespace tokens and 'from'".PHP_EOL;
×
1775
                }
1776

1777
                continue;
40✔
1778
            } else if (PHP_VERSION_ID >= 80300
3,205✔
1779
                && $tokenIsArray === true
3,205✔
1780
                && $token[0] === T_YIELD_FROM
3,205✔
1781
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
3,205✔
1782
                && stripos($token[1], 'yield') === 0
3,205✔
1783
            ) {
1,043✔
1784
                /*
1785
                    Since PHP 8.3, "yield from" allows for comments and will
1786
                    swallow the comment in the `T_YIELD_FROM` token.
1787
                    We need to split this up to allow for sniffs handling comments.
1788
                */
1789

1790
                $finalTokens[$newStackPtr] = [
20✔
1791
                    'code'    => T_YIELD_FROM,
20✔
1792
                    'type'    => 'T_YIELD_FROM',
20✔
1793
                    'content' => substr($token[1], 0, 5),
20✔
1794
                ];
20✔
1795
                $newStackPtr++;
20✔
1796

1797
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
20✔
1798
                // Remove the PHP open tag token.
1799
                array_shift($yieldFromSubtokens);
20✔
1800
                // Add the "from" keyword.
1801
                $yieldFromSubtokens[] = [
20✔
1802
                    0 => T_YIELD_FROM,
20✔
1803
                    1 => substr($token[1], -4),
20✔
1804
                ];
20✔
1805

1806
                // Inject the new tokens into the token stack.
1807
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
20✔
1808
                $numTokens = count($tokens);
20✔
1809

1810
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
20✔
1811
                    echo "\t\t* token $stackPtr split into parts (yield from with comment)".PHP_EOL;
×
1812
                }
1813

1814
                unset($yieldFromSubtokens);
20✔
1815
                continue;
20✔
1816
            }//end if
1817

1818
            /*
1819
                Before PHP 5.6, the ... operator was tokenized as three
1820
                T_STRING_CONCAT tokens in a row. So look for and combine
1821
                these tokens in earlier versions.
1822
            */
1823

1824
            if ($tokenIsArray === false
2,162✔
1825
                && $token[0] === '.'
3,205✔
1826
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1827
                && isset($tokens[($stackPtr + 2)]) === true
3,205✔
1828
                && $tokens[($stackPtr + 1)] === '.'
3,205✔
1829
                && $tokens[($stackPtr + 2)] === '.'
3,205✔
1830
            ) {
1,043✔
1831
                $newToken            = [];
216✔
1832
                $newToken['code']    = T_ELLIPSIS;
216✔
1833
                $newToken['type']    = 'T_ELLIPSIS';
216✔
1834
                $newToken['content'] = '...';
216✔
1835
                $finalTokens[$newStackPtr] = $newToken;
216✔
1836

1837
                $newStackPtr++;
216✔
1838
                $stackPtr += 2;
216✔
1839
                continue;
216✔
1840
            }
1841

1842
            /*
1843
                Before PHP 5.6, the ** operator was tokenized as two
1844
                T_MULTIPLY tokens in a row. So look for and combine
1845
                these tokens in earlier versions.
1846
            */
1847

1848
            if ($tokenIsArray === false
2,162✔
1849
                && $token[0] === '*'
3,205✔
1850
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1851
                && $tokens[($stackPtr + 1)] === '*'
3,205✔
1852
            ) {
1,043✔
1853
                $newToken            = [];
×
1854
                $newToken['code']    = T_POW;
×
1855
                $newToken['type']    = 'T_POW';
×
1856
                $newToken['content'] = '**';
×
1857
                $finalTokens[$newStackPtr] = $newToken;
×
1858

1859
                $newStackPtr++;
×
1860
                $stackPtr++;
×
1861
                continue;
×
1862
            }
1863

1864
            /*
1865
                Before PHP 5.6, the **= operator was tokenized as
1866
                T_MULTIPLY followed by T_MUL_EQUAL. So look for and combine
1867
                these tokens in earlier versions.
1868
            */
1869

1870
            if ($tokenIsArray === false
2,162✔
1871
                && $token[0] === '*'
3,205✔
1872
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1873
                && is_array($tokens[($stackPtr + 1)]) === true
3,205✔
1874
                && $tokens[($stackPtr + 1)][1] === '*='
3,205✔
1875
            ) {
1,043✔
1876
                $newToken            = [];
×
1877
                $newToken['code']    = T_POW_EQUAL;
×
1878
                $newToken['type']    = 'T_POW_EQUAL';
×
1879
                $newToken['content'] = '**=';
×
1880
                $finalTokens[$newStackPtr] = $newToken;
×
1881

1882
                $newStackPtr++;
×
1883
                $stackPtr++;
×
1884
                continue;
×
1885
            }
1886

1887
            /*
1888
                Before PHP 7, the ??= operator was tokenized as
1889
                T_INLINE_THEN, T_INLINE_THEN, T_EQUAL.
1890
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1891
                T_COALESCE, T_EQUAL.
1892
                So look for and combine these tokens in earlier versions.
1893
            */
1894

1895
            if (($tokenIsArray === false
2,162✔
1896
                && $token[0] === '?'
3,205✔
1897
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1898
                && $tokens[($stackPtr + 1)][0] === '?'
3,205✔
1899
                && isset($tokens[($stackPtr + 2)]) === true
3,205✔
1900
                && $tokens[($stackPtr + 2)][0] === '=')
1,043✔
1901
                || ($tokenIsArray === true
2,162✔
1902
                && $token[0] === T_COALESCE
3,205✔
1903
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1904
                && $tokens[($stackPtr + 1)][0] === '=')
3,205✔
1905
            ) {
1,043✔
1906
                $newToken            = [];
×
1907
                $newToken['code']    = T_COALESCE_EQUAL;
×
1908
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1909
                $newToken['content'] = '??=';
×
1910
                $finalTokens[$newStackPtr] = $newToken;
×
1911

1912
                $newStackPtr++;
×
1913
                $stackPtr++;
×
1914

1915
                if ($tokenIsArray === false) {
×
1916
                    // Pre PHP 7.
1917
                    $stackPtr++;
×
1918
                }
1919

1920
                continue;
×
1921
            }
1922

1923
            /*
1924
                Before PHP 7, the ?? operator was tokenized as
1925
                T_INLINE_THEN followed by T_INLINE_THEN.
1926
                So look for and combine these tokens in earlier versions.
1927
            */
1928

1929
            if ($tokenIsArray === false
2,162✔
1930
                && $token[0] === '?'
3,205✔
1931
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1932
                && $tokens[($stackPtr + 1)][0] === '?'
3,205✔
1933
            ) {
1,043✔
1934
                $newToken            = [];
61✔
1935
                $newToken['code']    = T_COALESCE;
61✔
1936
                $newToken['type']    = 'T_COALESCE';
61✔
1937
                $newToken['content'] = '??';
61✔
1938
                $finalTokens[$newStackPtr] = $newToken;
61✔
1939

1940
                $newStackPtr++;
61✔
1941
                $stackPtr++;
61✔
1942
                continue;
61✔
1943
            }
1944

1945
            /*
1946
                Before PHP 8, the ?-> operator was tokenized as
1947
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1948
                So look for and combine these tokens in earlier versions.
1949
            */
1950

1951
            if ($tokenIsArray === false
2,162✔
1952
                && $token[0] === '?'
3,205✔
1953
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1954
                && is_array($tokens[($stackPtr + 1)]) === true
3,205✔
1955
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
3,205✔
1956
            ) {
1,043✔
1957
                $newToken            = [];
54✔
1958
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
54✔
1959
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
54✔
1960
                $newToken['content'] = '?->';
54✔
1961
                $finalTokens[$newStackPtr] = $newToken;
54✔
1962

1963
                $newStackPtr++;
54✔
1964
                $stackPtr++;
54✔
1965
                continue;
54✔
1966
            }
1967

1968
            /*
1969
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1970
                tokens split the token with a T_STRING. So look for
1971
                and change these tokens in earlier versions.
1972
            */
1973

1974
            if (PHP_VERSION_ID < 70400
3,205✔
1975
                && ($tokenIsArray === true
3,205✔
1976
                && ($token[0] === T_LNUMBER
3,205✔
1977
                || $token[0] === T_DNUMBER)
3,205✔
1978
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
1979
                && is_array($tokens[($stackPtr + 1)]) === true
3,205✔
1980
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,205✔
1981
                && $tokens[($stackPtr + 1)][1][0] === '_')
3,205✔
1982
            ) {
1,043✔
1983
                $newContent = $token[1];
54✔
1984
                $newType    = $token[0];
54✔
1985
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
54✔
1986
                    if (is_array($tokens[$i]) === false) {
54✔
1987
                        break;
54✔
1988
                    }
1989

1990
                    if ($tokens[$i][0] === T_LNUMBER
54✔
1991
                        || $tokens[$i][0] === T_DNUMBER
54✔
1992
                    ) {
27✔
1993
                        $newContent .= $tokens[$i][1];
54✔
1994
                        continue;
54✔
1995
                    }
1996

1997
                    if ($tokens[$i][0] === T_STRING
54✔
1998
                        && $tokens[$i][1][0] === '_'
54✔
1999
                        && ((strpos($newContent, '0x') === 0
54✔
2000
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
54✔
2001
                        || (strpos($newContent, '0x') !== 0
54✔
2002
                        && substr($newContent, -1) !== '.'
54✔
2003
                        && substr(strtolower($newContent), -1) !== 'e'
54✔
2004
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
54✔
2005
                    ) {
27✔
2006
                        $newContent .= $tokens[$i][1];
54✔
2007

2008
                        // Support floats.
2009
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
54✔
2010
                            && ($tokens[($i + 1)] === '-'
54✔
2011
                            || $tokens[($i + 1)] === '+')
54✔
2012
                        ) {
27✔
2013
                            $newContent .= $tokens[($i + 1)];
54✔
2014
                            $i++;
54✔
2015
                        }
27✔
2016

2017
                        continue;
54✔
2018
                    }//end if
2019

2020
                    break;
54✔
2021
                }//end for
2022

2023
                if ($newType === T_LNUMBER
27✔
2024
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
2025
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
2026
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
2027
                    || (stripos($newContent, '0x') !== 0
54✔
2028
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
54✔
2029
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
54✔
2030
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
2031
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
54✔
2032
                ) {
27✔
2033
                    $newType = T_DNUMBER;
54✔
2034
                }
27✔
2035

2036
                $newToken            = [];
54✔
2037
                $newToken['code']    = $newType;
54✔
2038
                $newToken['type']    = Tokens::tokenName($newType);
54✔
2039
                $newToken['content'] = $newContent;
54✔
2040
                $finalTokens[$newStackPtr] = $newToken;
54✔
2041

2042
                $newStackPtr++;
54✔
2043
                $stackPtr = ($i - 1);
54✔
2044
                continue;
54✔
2045
            }//end if
2046

2047
            /*
2048
                Backfill the T_MATCH token for PHP versions < 8.0 and
2049
                do initial correction for non-match expression T_MATCH tokens
2050
                to T_STRING for PHP >= 8.0.
2051
                A final check for non-match expression T_MATCH tokens is done
2052
                in PHP::processAdditional().
2053
            */
2054

2055
            if ($tokenIsArray === true
2,162✔
2056
                && (($token[0] === T_STRING
3,205✔
2057
                && strtolower($token[1]) === 'match')
3,092✔
2058
                || $token[0] === T_MATCH)
3,205✔
2059
            ) {
1,043✔
2060
                $isMatch = false;
756✔
2061
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
756✔
2062
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
756✔
2063
                        continue;
756✔
2064
                    }
2065

2066
                    if ($tokens[$x] !== '(') {
756✔
2067
                        // This is not a match expression.
2068
                        break;
533✔
2069
                    }
2070

2071
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
756✔
2072
                        // Also not a match expression.
2073
                        break;
183✔
2074
                    }
2075

2076
                    $isMatch = true;
756✔
2077
                    break;
756✔
2078
                }//end for
2079

2080
                if ($isMatch === true && $token[0] === T_STRING) {
756✔
2081
                    $newToken            = [];
504✔
2082
                    $newToken['code']    = T_MATCH;
504✔
2083
                    $newToken['type']    = 'T_MATCH';
504✔
2084
                    $newToken['content'] = $token[1];
504✔
2085

2086
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
504✔
2087
                        echo "\t\t* token $stackPtr changed from T_STRING to T_MATCH".PHP_EOL;
×
2088
                    }
2089

2090
                    $finalTokens[$newStackPtr] = $newToken;
504✔
2091
                    $newStackPtr++;
504✔
2092
                    continue;
504✔
2093
                } else if ($isMatch === false && $token[0] === T_MATCH) {
724✔
2094
                    // PHP 8.0, match keyword, but not a match expression.
2095
                    $newToken            = [];
61✔
2096
                    $newToken['code']    = T_STRING;
61✔
2097
                    $newToken['type']    = 'T_STRING';
61✔
2098
                    $newToken['content'] = $token[1];
61✔
2099

2100
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
61✔
2101
                        echo "\t\t* token $stackPtr changed from T_MATCH to T_STRING".PHP_EOL;
×
2102
                    }
2103

2104
                    $finalTokens[$newStackPtr] = $newToken;
61✔
2105
                    $newStackPtr++;
61✔
2106
                    continue;
61✔
2107
                }//end if
2108
            }//end if
236✔
2109

2110
            /*
2111
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
2112
                to prevent scope being set and the scope for switch default statements
2113
                breaking.
2114
            */
2115

2116
            if ($tokenIsArray === true
2,162✔
2117
                && $token[0] === T_DEFAULT
3,205✔
2118
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,205✔
2119
            ) {
1,043✔
2120
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,449✔
2121
                    if ($tokens[$x] === ',') {
1,449✔
2122
                        // Skip over potential trailing comma (supported in PHP).
2123
                        continue;
231✔
2124
                    }
2125

2126
                    if (is_array($tokens[$x]) === false
1,449✔
2127
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
1,449✔
2128
                    ) {
483✔
2129
                        // Non-empty, non-comma content.
2130
                        break;
1,449✔
2131
                    }
2132
                }
252✔
2133

2134
                if (isset($tokens[$x]) === true
1,449✔
2135
                    && is_array($tokens[$x]) === true
1,449✔
2136
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,449✔
2137
                ) {
483✔
2138
                    // Modify the original token stack for the double arrow so that
2139
                    // future checks can disregard the double arrow token more easily.
2140
                    // For match expression "case" statements, this is handled
2141
                    // in PHP::processAdditional().
2142
                    $tokens[$x][0] = T_MATCH_ARROW;
756✔
2143
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
756✔
2144
                        echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
2145
                    }
2146

2147
                    $newToken            = [];
756✔
2148
                    $newToken['code']    = T_MATCH_DEFAULT;
756✔
2149
                    $newToken['type']    = 'T_MATCH_DEFAULT';
756✔
2150
                    $newToken['content'] = $token[1];
756✔
2151

2152
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
756✔
2153
                        echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
×
2154
                    }
2155

2156
                    $finalTokens[$newStackPtr] = $newToken;
756✔
2157
                    $newStackPtr++;
756✔
2158
                    continue;
756✔
2159
                }//end if
2160
            }//end if
483✔
2161

2162
            /*
2163
                Convert ? to T_NULLABLE OR T_INLINE_THEN
2164
            */
2165

2166
            if ($tokenIsArray === false && $token[0] === '?') {
3,205✔
2167
                $newToken            = [];
1,267✔
2168
                $newToken['content'] = '?';
1,267✔
2169

2170
                // For typed constants, we only need to check the token before the ? to be sure.
2171
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,267✔
2172
                    $newToken['code'] = T_NULLABLE;
151✔
2173
                    $newToken['type'] = 'T_NULLABLE';
151✔
2174

2175
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
151✔
2176
                        echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2177
                    }
2178

2179
                    $finalTokens[$newStackPtr] = $newToken;
151✔
2180
                    $newStackPtr++;
151✔
2181
                    continue;
151✔
2182
                }
2183

2184
                /*
2185
                 * Check if the next non-empty token is one of the tokens which can be used
2186
                 * in type declarations. If not, it's definitely a ternary.
2187
                 * At this point, the only token types which need to be taken into consideration
2188
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
2189
                 */
2190

2191
                $lastRelevantNonEmpty = null;
1,267✔
2192

2193
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,267✔
2194
                    if (is_array($tokens[$i]) === true) {
1,267✔
2195
                        $tokenType = $tokens[$i][0];
1,267✔
2196
                    } else {
397✔
2197
                        $tokenType = $tokens[$i];
1,034✔
2198
                    }
2199

2200
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
1,267✔
2201
                        continue;
1,267✔
2202
                    }
2203

2204
                    if ($tokenType === T_STRING
870✔
2205
                        || $tokenType === T_NAME_FULLY_QUALIFIED
1,267✔
2206
                        || $tokenType === T_NAME_RELATIVE
1,267✔
2207
                        || $tokenType === T_NAME_QUALIFIED
1,267✔
2208
                        || $tokenType === T_ARRAY
1,267✔
2209
                        || $tokenType === T_NAMESPACE
1,267✔
2210
                        || $tokenType === T_NS_SEPARATOR
1,267✔
2211
                    ) {
397✔
2212
                        $lastRelevantNonEmpty = $tokenType;
1,034✔
2213
                        continue;
1,034✔
2214
                    }
2215

2216
                    if (($tokenType !== T_CALLABLE
870✔
2217
                        && isset($lastRelevantNonEmpty) === false)
1,267✔
2218
                        || ($lastRelevantNonEmpty === T_ARRAY
669✔
2219
                        && $tokenType === '(')
1,034✔
2220
                        || (($lastRelevantNonEmpty === T_STRING
1,165✔
2221
                        || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED
800✔
2222
                        || $lastRelevantNonEmpty === T_NAME_RELATIVE
435✔
2223
                        || $lastRelevantNonEmpty === T_NAME_QUALIFIED)
800✔
2224
                        && ($tokenType === T_DOUBLE_COLON
1,165✔
2225
                        || $tokenType === '('
1,165✔
2226
                        || $tokenType === ':'))
1,235✔
2227
                    ) {
397✔
2228
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,048✔
2229
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2230
                        }
2231

2232
                        $newToken['code'] = T_INLINE_THEN;
1,048✔
2233
                        $newToken['type'] = 'T_INLINE_THEN';
1,048✔
2234

2235
                        $insideInlineIf[] = $stackPtr;
1,048✔
2236

2237
                        $finalTokens[$newStackPtr] = $newToken;
1,048✔
2238
                        $newStackPtr++;
1,048✔
2239
                        continue 2;
1,048✔
2240
                    }
2241

2242
                    break;
219✔
2243
                }//end for
2244

2245
                /*
2246
                 * This can still be a nullable type or a ternary.
2247
                 * Do additional checking.
2248
                 */
2249

2250
                $prevNonEmpty     = null;
240✔
2251
                $lastSeenNonEmpty = null;
240✔
2252

2253
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
240✔
2254
                    if (is_array($tokens[$i]) === true) {
240✔
2255
                        $tokenType = $tokens[$i][0];
240✔
2256
                    } else {
80✔
2257
                        $tokenType = $tokens[$i];
240✔
2258
                    }
2259

2260
                    if ($tokenType === T_STATIC
160✔
2261
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
160✔
2262
                        || $lastSeenNonEmpty === '(')
160✔
2263
                    ) {
80✔
2264
                        $lastSeenNonEmpty = $tokenType;
×
2265
                        continue;
×
2266
                    }
2267

2268
                    if ($prevNonEmpty === null
160✔
2269
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
240✔
2270
                    ) {
80✔
2271
                        // Found the previous non-empty token.
2272
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
240✔
2273
                            $newToken['code'] = T_NULLABLE;
201✔
2274
                            $newToken['type'] = 'T_NULLABLE';
201✔
2275

2276
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
201✔
2277
                                echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2278
                            }
2279

2280
                            break;
201✔
2281
                        }
2282

2283
                        $prevNonEmpty = $tokenType;
240✔
2284
                    }
80✔
2285

2286
                    if ($tokenType === T_FUNCTION
160✔
2287
                        || $tokenType === T_FN
240✔
2288
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
240✔
2289
                        || isset(Tokens::$scopeModifiers[$tokenType]) === true
240✔
2290
                        || $tokenType === T_VAR
240✔
2291
                        || $tokenType === T_READONLY
240✔
2292
                    ) {
80✔
2293
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
219✔
2294
                            echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2295
                        }
2296

2297
                        $newToken['code'] = T_NULLABLE;
219✔
2298
                        $newToken['type'] = 'T_NULLABLE';
219✔
2299
                        break;
219✔
2300
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
240✔
2301
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
39✔
2302
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2303
                        }
2304

2305
                        $newToken['code'] = T_INLINE_THEN;
39✔
2306
                        $newToken['type'] = 'T_INLINE_THEN';
39✔
2307

2308
                        $insideInlineIf[] = $stackPtr;
39✔
2309
                        break;
39✔
2310
                    }
2311

2312
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
240✔
2313
                        $lastSeenNonEmpty = $tokenType;
240✔
2314
                    }
80✔
2315
                }//end for
80✔
2316

2317
                $finalTokens[$newStackPtr] = $newToken;
240✔
2318
                $newStackPtr++;
240✔
2319
                continue;
240✔
2320
            }//end if
2321

2322
            /*
2323
                Tokens after a double colon may look like scope openers,
2324
                such as when writing code like Foo::NAMESPACE, but they are
2325
                only ever variables or strings.
2326
            */
2327

2328
            if ($stackPtr > 1
2,162✔
2329
                && (is_array($tokens[($stackPtr - 1)]) === true
3,205✔
2330
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
3,205✔
2331
                && $tokenIsArray === true
3,205✔
2332
                && $token[0] !== T_STRING
3,205✔
2333
                && $token[0] !== T_VARIABLE
3,205✔
2334
                && $token[0] !== T_DOLLAR
3,205✔
2335
                && isset(Tokens::$emptyTokens[$token[0]]) === false
3,205✔
2336
            ) {
1,043✔
2337
                $newToken            = [];
×
2338
                $newToken['code']    = T_STRING;
×
2339
                $newToken['type']    = 'T_STRING';
×
2340
                $newToken['content'] = $token[1];
×
2341
                $finalTokens[$newStackPtr] = $newToken;
×
2342

2343
                $newStackPtr++;
×
2344
                continue;
×
2345
            }
2346

2347
            /*
2348
                Backfill the T_FN token for PHP versions < 7.4.
2349
            */
2350

2351
            if ($tokenIsArray === true
2,162✔
2352
                && $token[0] === T_STRING
3,205✔
2353
                && strtolower($token[1]) === 'fn'
3,205✔
2354
            ) {
1,043✔
2355
                // Modify the original token stack so that
2356
                // future checks (like looking for T_NULLABLE) can
2357
                // detect the T_FN token more easily.
2358
                $tokens[$stackPtr][0] = T_FN;
1,308✔
2359
                $token[0] = T_FN;
1,308✔
2360
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,308✔
2361
                    echo "\t\t* token $stackPtr changed from T_STRING to T_FN".PHP_EOL;
×
2362
                }
2363
            }
654✔
2364

2365
            /*
2366
                This is a special condition for T_ARRAY tokens used for
2367
                function return types. We want to keep the parenthesis map clean,
2368
                so let's tag these tokens as T_STRING.
2369
            */
2370

2371
            if ($tokenIsArray === true
2,162✔
2372
                && ($token[0] === T_FUNCTION
3,205✔
2373
                || $token[0] === T_FN)
3,205✔
2374
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
3,205✔
2375
            ) {
1,043✔
2376
                // Go looking for the colon to start the return type hint.
2377
                // Start by finding the closing parenthesis of the function.
2378
                $parenthesisStack  = [];
2,562✔
2379
                $parenthesisCloser = false;
2,562✔
2380
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,562✔
2381
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,562✔
2382
                        $parenthesisStack[] = $x;
2,562✔
2383
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,562✔
2384
                        array_pop($parenthesisStack);
2,562✔
2385
                        if (empty($parenthesisStack) === true) {
2,562✔
2386
                            $parenthesisCloser = $x;
2,562✔
2387
                            break;
2,562✔
2388
                        }
2389
                    }
202✔
2390
                }
854✔
2391

2392
                if ($parenthesisCloser !== false) {
2,562✔
2393
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,562✔
2394
                        if (is_array($tokens[$x]) === false
2,562✔
2395
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
2,562✔
2396
                        ) {
854✔
2397
                            // Non-empty content.
2398
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,562✔
2399
                                // Found a use statement, so search ahead for the closing parenthesis.
2400
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
2401
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
2402
                                        continue(2);
57✔
2403
                                    }
2404
                                }
19✔
2405
                            }
2406

2407
                            break;
2,562✔
2408
                        }
2409
                    }
832✔
2410

2411
                    if (isset($tokens[$x]) === true
2,562✔
2412
                        && is_array($tokens[$x]) === false
2,562✔
2413
                        && $tokens[$x] === ':'
2,562✔
2414
                    ) {
854✔
2415
                        // Find the start of the return type.
2416
                        for ($x += 1; $x < $numTokens; $x++) {
2,061✔
2417
                            if (is_array($tokens[$x]) === true
2,061✔
2418
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
2,061✔
2419
                            ) {
687✔
2420
                                // Whitespace or comments before the return type.
2421
                                continue;
2,061✔
2422
                            }
2423

2424
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
2,061✔
2425
                                // Found a nullable operator, so skip it.
2426
                                // But also convert the token to save the tokenizer
2427
                                // a bit of time later on.
2428
                                $tokens[$x] = [
201✔
2429
                                    T_NULLABLE,
201✔
2430
                                    '?',
201✔
2431
                                ];
67✔
2432

2433
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
201✔
2434
                                    echo "\t\t* token $x changed from ? to T_NULLABLE".PHP_EOL;
×
2435
                                }
2436

2437
                                continue;
201✔
2438
                            }
2439

2440
                            break;
2,061✔
2441
                        }//end for
2442
                    }//end if
687✔
2443
                }//end if
854✔
2444
            }//end if
854✔
2445

2446
            /*
2447
                Before PHP 7, the <=> operator was tokenized as
2448
                T_IS_SMALLER_OR_EQUAL followed by T_GREATER_THAN.
2449
                So look for and combine these tokens in earlier versions.
2450
            */
2451

2452
            if ($tokenIsArray === true
2,162✔
2453
                && $token[0] === T_IS_SMALLER_OR_EQUAL
3,205✔
2454
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
2455
                && $tokens[($stackPtr + 1)][0] === '>'
3,205✔
2456
            ) {
1,043✔
2457
                $newToken            = [];
×
2458
                $newToken['code']    = T_SPACESHIP;
×
2459
                $newToken['type']    = 'T_SPACESHIP';
×
2460
                $newToken['content'] = '<=>';
×
2461
                $finalTokens[$newStackPtr] = $newToken;
×
2462

2463
                $newStackPtr++;
×
2464
                $stackPtr++;
×
2465
                continue;
×
2466
            }
2467

2468
            /*
2469
                PHP doesn't assign a token to goto labels, so we have to.
2470
                These are just string tokens with a single colon after them. Double
2471
                colons are already tokenized and so don't interfere with this check.
2472
                But we do have to account for CASE statements, that look just like
2473
                goto labels.
2474
            */
2475

2476
            if ($tokenIsArray === true
2,162✔
2477
                && $token[0] === T_STRING
3,205✔
2478
                && isset($tokens[($stackPtr + 1)]) === true
3,205✔
2479
                && $tokens[($stackPtr + 1)] === ':'
3,205✔
2480
                && (is_array($tokens[($stackPtr - 1)]) === false
2,590✔
2481
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
2,628✔
2482
            ) {
1,043✔
2483
                $stopTokens = [
466✔
2484
                    T_CASE               => true,
1,398✔
2485
                    T_SEMICOLON          => true,
1,398✔
2486
                    T_OPEN_TAG           => true,
1,398✔
2487
                    T_OPEN_CURLY_BRACKET => true,
1,398✔
2488
                    T_INLINE_THEN        => true,
1,398✔
2489
                    T_ENUM               => true,
1,398✔
2490
                ];
932✔
2491

2492
                for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,398✔
2493
                    if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,398✔
2494
                        break;
1,398✔
2495
                    }
2496
                }
466✔
2497

2498
                if ($finalTokens[$x]['code'] !== T_CASE
1,398✔
2499
                    && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,398✔
2500
                    && $finalTokens[$x]['code'] !== T_ENUM
1,398✔
2501
                ) {
466✔
2502
                    $finalTokens[$newStackPtr] = [
111✔
2503
                        'content' => $token[1].':',
111✔
2504
                        'code'    => T_GOTO_LABEL,
111✔
2505
                        'type'    => 'T_GOTO_LABEL',
111✔
2506
                    ];
37✔
2507

2508
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
111✔
2509
                        echo "\t\t* token $stackPtr changed from T_STRING to T_GOTO_LABEL".PHP_EOL;
×
2510
                        echo "\t\t* skipping T_COLON token ".($stackPtr + 1).PHP_EOL;
×
2511
                    }
2512

2513
                    $newStackPtr++;
111✔
2514
                    $stackPtr++;
111✔
2515
                    continue;
111✔
2516
                }
2517
            }//end if
466✔
2518

2519
            /*
2520
                If this token has newlines in its content, split each line up
2521
                and create a new token for each line. We do this so it's easier
2522
                to ascertain where errors occur on a line.
2523
                Note that $token[1] is the token's content.
2524
            */
2525

2526
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
3,205✔
2527
                $tokenLines = explode($this->eolChar, $token[1]);
3,205✔
2528
                $numLines   = count($tokenLines);
3,205✔
2529
                $newToken   = [
1,081✔
2530
                    'type'    => Tokens::tokenName($token[0]),
3,205✔
2531
                    'code'    => $token[0],
3,205✔
2532
                    'content' => '',
3,205✔
2533
                ];
2,124✔
2534

2535
                for ($i = 0; $i < $numLines; $i++) {
3,205✔
2536
                    $newToken['content'] = $tokenLines[$i];
3,205✔
2537
                    if ($i === ($numLines - 1)) {
3,205✔
2538
                        if ($tokenLines[$i] === '') {
3,205✔
2539
                            break;
3,205✔
2540
                        }
2541
                    } else {
965✔
2542
                        $newToken['content'] .= $this->eolChar;
3,205✔
2543
                    }
2544

2545
                    $finalTokens[$newStackPtr] = $newToken;
3,205✔
2546
                    $newStackPtr++;
3,205✔
2547
                }
1,043✔
2548
            } else {
1,043✔
2549
                // Some T_STRING tokens should remain that way due to their context.
2550
                if ($tokenIsArray === true && $token[0] === T_STRING) {
3,205✔
2551
                    $preserveTstring = false;
2,866✔
2552

2553
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2554
                    // but the constant name should not be.
2555
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,866✔
2556
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,790✔
2557
                        || $insideConstDeclaration === true
1,936✔
2558
                    ) {
930✔
2559
                        // Find the next non-empty token.
2560
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,752✔
2561
                            if (is_array($tokens[$i]) === true
1,752✔
2562
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,752✔
2563
                            ) {
583✔
2564
                                continue;
1,551✔
2565
                            }
2566

2567
                            break;
1,752✔
2568
                        }
2569

2570
                        if ($tokens[$i] === '=') {
1,752✔
2571
                            $preserveTstring        = true;
1,491✔
2572
                            $insideConstDeclaration = false;
1,578✔
2573
                        }
496✔
2574
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,866✔
2575
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,866✔
2576
                    ) {
930✔
2577
                        $preserveTstring   = true;
2,752✔
2578
                        $tokenContentLower = strtolower($token[1]);
2,752✔
2579

2580
                        // Special case for syntax like: return new self/new parent
2581
                        // where self/parent should not be a string.
2582
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,752✔
2583
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,752✔
2584
                        ) {
892✔
2585
                            $preserveTstring = false;
1,146✔
2586
                        }
382✔
2587

2588
                        // Special case for fully qualified \true, \false and \null
2589
                        // where true/false/null should not be a string.
2590
                        // Note: if this is the _start_ of a longer namespaced name, this will undone again later.
2591
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
2,752✔
2592
                            && ($tokenContentLower === 'true' || $tokenContentLower === 'false' || $tokenContentLower === 'null')
2,752✔
2593
                        ) {
892✔
2594
                            for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,163✔
2595
                                if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
466✔
2596
                                    continue;
466✔
2597
                                }
2598

2599
                                if ($finalTokens[$i]['code'] !== T_STRING
466✔
2600
                                    && $finalTokens[$i]['code'] !== T_NAMESPACE
466✔
2601
                                ) {
233✔
2602
                                    $preserveTstring = false;
466✔
2603
                                }
233✔
2604

2605
                                break;
466✔
2606
                            }
2607
                        }
233✔
2608
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,852✔
2609
                        // Function names for functions declared to return by reference.
2610
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,080✔
2611
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,080✔
2612
                                continue;
498✔
2613
                            }
2614

2615
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,080✔
2616
                                $preserveTstring = true;
498✔
2617
                            }
166✔
2618

2619
                            break;
1,080✔
2620
                        }
2621
                    } else {
360✔
2622
                        // Keywords with special PHPCS token when used as a function call.
2623
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,845✔
2624
                            if (is_array($tokens[$i]) === true
2,845✔
2625
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,845✔
2626
                            ) {
923✔
2627
                                continue;
2,376✔
2628
                            }
2629

2630
                            if ($tokens[$i][0] === '(') {
2,845✔
2631
                                $preserveTstring = true;
2,424✔
2632
                            }
808✔
2633

2634
                            break;
2,845✔
2635
                        }
2636
                    }//end if
2637

2638
                    if ($preserveTstring === true) {
2,866✔
2639
                        $finalTokens[$newStackPtr] = [
2,755✔
2640
                            'code'    => T_STRING,
2,755✔
2641
                            'type'    => 'T_STRING',
2,755✔
2642
                            'content' => $token[1],
2,755✔
2643
                        ];
931✔
2644

2645
                        $newStackPtr++;
2,755✔
2646
                        continue;
2,755✔
2647
                    }
2648
                }//end if
890✔
2649

2650
                $newToken = null;
3,205✔
2651
                if ($tokenIsArray === false) {
3,205✔
2652
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3,193✔
2653
                        $newToken = self::$resolveTokenCache[$token[0]];
3,193✔
2654
                    }
1,039✔
2655
                } else {
1,039✔
2656
                    $cacheKey = null;
3,205✔
2657
                    if ($token[0] === T_STRING) {
3,205✔
2658
                        $cacheKey = strtolower($token[1]);
2,746✔
2659
                    } else if ($token[0] !== T_CURLY_OPEN) {
3,205✔
2660
                        $cacheKey = $token[0];
3,205✔
2661
                    }
1,043✔
2662

2663
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,205✔
2664
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,205✔
2665
                        $newToken['content'] = $token[1];
3,205✔
2666
                    }
1,043✔
2667
                }
2668

2669
                if ($newToken === null) {
3,205✔
2670
                    $newToken = self::standardiseToken($token);
55✔
2671
                }
18✔
2672

2673
                // Convert colons that are actually the ELSE component of an
2674
                // inline IF statement.
2675
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
3,205✔
2676
                    $isInlineIf = true;
1,048✔
2677

2678
                    // Make sure this isn't a named parameter label.
2679
                    // Get the previous non-empty token.
2680
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,048✔
2681
                        if (is_array($tokens[$i]) === false
1,048✔
2682
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,048✔
2683
                        ) {
324✔
2684
                            break;
1,048✔
2685
                        }
2686
                    }
324✔
2687

2688
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,048✔
2689
                        $isInlineIf = false;
648✔
2690
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
648✔
2691
                            echo "\t\t* token is parameter label, not T_INLINE_ELSE".PHP_EOL;
×
2692
                        }
2693
                    }
216✔
2694

2695
                    if ($isInlineIf === true) {
1,048✔
2696
                        // Make sure this isn't a return type separator.
2697
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,048✔
2698
                            if (is_array($tokens[$i]) === false
1,048✔
2699
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,048✔
2700
                                && $tokens[$i][0] !== T_COMMENT
1,048✔
2701
                                && $tokens[$i][0] !== T_WHITESPACE)
1,048✔
2702
                            ) {
324✔
2703
                                break;
1,048✔
2704
                            }
2705
                        }
324✔
2706

2707
                        if ($tokens[$i] === ')') {
1,048✔
2708
                            $parenCount = 1;
648✔
2709
                            for ($i--; $i > 0; $i--) {
648✔
2710
                                if ($tokens[$i] === '(') {
648✔
2711
                                    $parenCount--;
648✔
2712
                                    if ($parenCount === 0) {
648✔
2713
                                        break;
648✔
2714
                                    }
2715
                                } else if ($tokens[$i] === ')') {
648✔
2716
                                    $parenCount++;
×
2717
                                }
2718
                            }
216✔
2719

2720
                            // We've found the open parenthesis, so if the previous
2721
                            // non-empty token is FUNCTION or USE, this is a return type.
2722
                            // Note that we need to skip T_STRING tokens here as these
2723
                            // can be function names.
2724
                            for ($i--; $i > 0; $i--) {
648✔
2725
                                if (is_array($tokens[$i]) === false
648✔
2726
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
648✔
2727
                                    && $tokens[$i][0] !== T_COMMENT
648✔
2728
                                    && $tokens[$i][0] !== T_WHITESPACE
648✔
2729
                                    && $tokens[$i][0] !== T_STRING)
648✔
2730
                                ) {
216✔
2731
                                    break;
648✔
2732
                                }
2733
                            }
216✔
2734

2735
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
648✔
2736
                                $isInlineIf = false;
648✔
2737
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
648✔
2738
                                    echo "\t\t* token is return type, not T_INLINE_ELSE".PHP_EOL;
×
2739
                                }
2740
                            }
216✔
2741
                        }//end if
216✔
2742
                    }//end if
324✔
2743

2744
                    // Check to see if this is a CASE or DEFAULT opener.
2745
                    if ($isInlineIf === true) {
1,048✔
2746
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,048✔
2747
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,048✔
2748
                            if (is_array($tokens[$i]) === true
1,048✔
2749
                                && ($tokens[$i][0] === T_CASE
1,048✔
2750
                                || $tokens[$i][0] === T_DEFAULT)
1,048✔
2751
                            ) {
324✔
2752
                                $isInlineIf = false;
×
2753
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2754
                                    echo "\t\t* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE".PHP_EOL;
×
2755
                                }
2756

2757
                                break;
×
2758
                            }
2759

2760
                            if (is_array($tokens[$i]) === false
1,048✔
2761
                                && ($tokens[$i] === ';'
1,048✔
2762
                                || $tokens[$i] === '{'
1,048✔
2763
                                || $tokens[$i] === '}')
1,048✔
2764
                            ) {
324✔
2765
                                break;
831✔
2766
                            }
2767
                        }//end for
324✔
2768
                    }//end if
324✔
2769

2770
                    if ($isInlineIf === true) {
1,048✔
2771
                        array_pop($insideInlineIf);
1,048✔
2772
                        $newToken['code'] = T_INLINE_ELSE;
1,048✔
2773
                        $newToken['type'] = 'T_INLINE_ELSE';
1,048✔
2774

2775
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,048✔
2776
                            echo "\t\t* token changed from T_COLON to T_INLINE_ELSE".PHP_EOL;
×
2777
                        }
2778
                    }
324✔
2779
                }//end if
324✔
2780

2781
                // This is a special condition for T_ARRAY tokens used for anything else
2782
                // but array declarations, like type hinting function arguments as
2783
                // being arrays.
2784
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2785
                // T_STRING.
2786
                if ($newToken['code'] === T_ARRAY) {
3,205✔
2787
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,513✔
2788
                        if (is_array($tokens[$i]) === false
1,513✔
2789
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,513✔
2790
                        ) {
479✔
2791
                            // Non-empty content.
2792
                            break;
1,513✔
2793
                        }
2794
                    }
402✔
2795

2796
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,513✔
2797
                        $newToken['code'] = T_STRING;
1,282✔
2798
                        $newToken['type'] = 'T_STRING';
1,282✔
2799
                    }
402✔
2800
                }
479✔
2801

2802
                // This is a special case when checking PHP 5.5+ code in PHP < 5.5
2803
                // where "finally" should be T_FINALLY instead of T_STRING.
2804
                if ($newToken['code'] === T_STRING
3,205✔
2805
                    && strtolower($newToken['content']) === 'finally'
3,205✔
2806
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CLOSE_CURLY_BRACKET
3,205✔
2807
                ) {
1,043✔
2808
                    $newToken['code'] = T_FINALLY;
181✔
2809
                    $newToken['type'] = 'T_FINALLY';
181✔
2810
                }
181✔
2811

2812
                // This is a special case for PHP 5.6 use function and use const
2813
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2814
                // and T_CONST.
2815
                if (($newToken['code'] === T_FUNCTION
3,205✔
2816
                    || $newToken['code'] === T_CONST)
3,205✔
2817
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
3,205✔
2818
                ) {
1,043✔
2819
                    $newToken['code'] = T_STRING;
201✔
2820
                    $newToken['type'] = 'T_STRING';
201✔
2821
                }
67✔
2822

2823
                // This is a special case for use groups in PHP 7+ where leaving
2824
                // the curly braces as their normal tokens would confuse
2825
                // the scope map and sniffs.
2826
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
3,205✔
2827
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
3,205✔
2828
                ) {
1,043✔
2829
                    $newToken['code'] = T_OPEN_USE_GROUP;
201✔
2830
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
201✔
2831
                    $insideUseGroup   = true;
201✔
2832
                }
67✔
2833

2834
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,205✔
2835
                    $newToken['code'] = T_CLOSE_USE_GROUP;
201✔
2836
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
201✔
2837
                    $insideUseGroup   = false;
201✔
2838
                }
67✔
2839

2840
                $finalTokens[$newStackPtr] = $newToken;
3,205✔
2841
                $newStackPtr++;
3,205✔
2842
            }//end if
2843
        }//end for
1,043✔
2844

2845
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,205✔
2846
            echo "\t*** END PHP TOKENIZING ***".PHP_EOL;
×
2847
        }
2848

2849
        return $finalTokens;
3,205✔
2850

2851
    }//end tokenize()
2852

2853

2854
    /**
2855
     * Performs additional processing after main tokenizing.
2856
     *
2857
     * This additional processing checks for CASE statements that are using curly
2858
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2859
     * into T_CLOSURE when they are not standard function definitions. It also
2860
     * detects short array syntax and converts those square brackets into new tokens.
2861
     * It also corrects some usage of the static and class keywords. It also
2862
     * assigns tokens to function return types.
2863
     *
2864
     * @return void
2865
     */
2866
    protected function processAdditional()
1,855✔
2867
    {
2868
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,855✔
2869
            echo "\t*** START ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
2870
        }
2871

2872
        $this->createAttributesNestingMap();
1,855✔
2873

2874
        $numTokens         = count($this->tokens);
1,855✔
2875
        $lastSeenTypeToken = $numTokens;
1,855✔
2876

2877
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,855✔
2878
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2879
            if (isset($this->tokens[$i]['scope_opener']) === true
1,855✔
2880
                && isset($this->tokens[$i]['scope_condition']) === false
1,855✔
2881
            ) {
593✔
2882
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2883
            }
2884

2885
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,855✔
2886
                /*
2887
                    Detect functions that are actually closures and
2888
                    assign them a different token.
2889
                */
2890

2891
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,599✔
2892
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,587✔
2893
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,587✔
2894
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,587✔
2895
                        ) {
529✔
2896
                            break;
1,587✔
2897
                        }
2898
                    }
529✔
2899

2900
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,587✔
2901
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,110✔
2902
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,110✔
2903
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,110✔
2904
                            $line = $this->tokens[$i]['line'];
×
2905
                            echo "\t* token $i on line $line changed from T_FUNCTION to T_CLOSURE".PHP_EOL;
×
2906
                        }
2907

2908
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,110✔
2909
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
660✔
2910
                                continue;
×
2911
                            }
2912

2913
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
660✔
2914
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
660✔
2915
                                $type = $this->tokens[$x]['type'];
×
2916
                                echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2917
                            }
2918
                        }
220✔
2919
                    }
370✔
2920
                }//end if
529✔
2921

2922
                continue;
1,599✔
2923
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,855✔
2924
                /*
2925
                    Detect anonymous classes and assign them a different token.
2926
                */
2927

2928
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,717✔
2929
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,717✔
2930
                        break;
1,717✔
2931
                    }
2932
                }
547✔
2933

2934
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,717✔
2935
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,717✔
2936
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,699✔
2937
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,708✔
2938
                ) {
547✔
2939
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,183✔
2940
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,183✔
2941
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,183✔
2942
                        $line = $this->tokens[$i]['line'];
×
2943
                        echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL;
×
2944
                    }
2945

2946
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,183✔
2947
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,183✔
2948
                    ) {
369✔
2949
                        $closer = $this->tokens[$x]['parenthesis_closer'];
733✔
2950

2951
                        $this->tokens[$i]['parenthesis_opener']     = $x;
733✔
2952
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
733✔
2953
                        $this->tokens[$i]['parenthesis_owner']      = $i;
733✔
2954
                        $this->tokens[$x]['parenthesis_owner']      = $i;
733✔
2955
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
733✔
2956

2957
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
733✔
2958
                            $line = $this->tokens[$i]['line'];
×
2959
                            echo "\t\t* added parenthesis keys to T_ANON_CLASS token $i on line $line".PHP_EOL;
×
2960
                        }
2961
                    }
219✔
2962

2963
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,183✔
2964
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,069✔
2965
                            continue;
×
2966
                        }
2967

2968
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,069✔
2969
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,069✔
2970
                            $type = $this->tokens[$x]['type'];
×
2971
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2972
                        }
2973
                    }
331✔
2974
                }//end if
369✔
2975

2976
                continue;
1,717✔
2977
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,855✔
2978
                // Possible arrow function.
2979
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,194✔
2980
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,194✔
2981
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,194✔
2982
                    ) {
398✔
2983
                        // Non-whitespace content.
2984
                        break;
1,194✔
2985
                    }
2986
                }
364✔
2987

2988
                if (isset($this->tokens[$x]) === true
1,194✔
2989
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,194✔
2990
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,194✔
2991
                ) {
398✔
2992
                    $ignore  = Tokens::$emptyTokens;
1,191✔
2993
                    $ignore += [
397✔
2994
                        T_ARRAY                  => T_ARRAY,
1,191✔
2995
                        T_CALLABLE               => T_CALLABLE,
1,191✔
2996
                        T_COLON                  => T_COLON,
1,191✔
2997
                        T_NAMESPACE              => T_NAMESPACE,
1,191✔
2998
                        T_NS_SEPARATOR           => T_NS_SEPARATOR,
1,191✔
2999
                        T_NULL                   => T_NULL,
1,191✔
3000
                        T_TRUE                   => T_TRUE,
1,191✔
3001
                        T_FALSE                  => T_FALSE,
1,191✔
3002
                        T_NULLABLE               => T_NULLABLE,
1,191✔
3003
                        T_PARENT                 => T_PARENT,
1,191✔
3004
                        T_SELF                   => T_SELF,
1,191✔
3005
                        T_STATIC                 => T_STATIC,
1,191✔
3006
                        T_STRING                 => T_STRING,
1,191✔
3007
                        T_TYPE_UNION             => T_TYPE_UNION,
1,191✔
3008
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,191✔
3009
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,191✔
3010
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,191✔
3011
                    ];
397✔
3012

3013
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,191✔
3014
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,191✔
3015
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,191✔
3016
                            break;
1,191✔
3017
                        }
3018
                    }
397✔
3019

3020
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,191✔
3021
                        $endTokens = [
397✔
3022
                            T_COLON                => true,
1,191✔
3023
                            T_COMMA                => true,
1,191✔
3024
                            T_SEMICOLON            => true,
1,191✔
3025
                            T_CLOSE_PARENTHESIS    => true,
1,191✔
3026
                            T_CLOSE_SQUARE_BRACKET => true,
1,191✔
3027
                            T_CLOSE_CURLY_BRACKET  => true,
1,191✔
3028
                            T_CLOSE_SHORT_ARRAY    => true,
1,191✔
3029
                            T_OPEN_TAG             => true,
1,191✔
3030
                            T_CLOSE_TAG            => true,
1,191✔
3031
                        ];
794✔
3032

3033
                        $inTernary    = false;
1,191✔
3034
                        $lastEndToken = null;
1,191✔
3035

3036
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,191✔
3037
                            // Arrow function closer should never be shared with the closer of a match
3038
                            // control structure.
3039
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,191✔
3040
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,191✔
3041
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,191✔
3042
                            ) {
397✔
3043
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
423✔
3044
                                    // Match in return value of arrow function. Move on to the next token.
3045
                                    continue;
423✔
3046
                                }
3047

3048
                                // Arrow function as return value for the last match case without trailing comma.
3049
                                if ($lastEndToken !== null) {
423✔
3050
                                    $scopeCloser = $lastEndToken;
423✔
3051
                                    break;
423✔
3052
                                }
3053

3054
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
3055
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
3056
                                        $scopeCloser = $lastNonEmpty;
186✔
3057
                                        break 2;
186✔
3058
                                    }
3059
                                }
62✔
3060
                            }
3061

3062
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,191✔
3063
                                if ($lastEndToken !== null
794✔
3064
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
1,041✔
3065
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
706✔
3066
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
891✔
3067
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
1,041✔
3068
                                ) {
397✔
3069
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
3070
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
3071
                                            $scopeCloser = $lastNonEmpty;
186✔
3072
                                            break;
186✔
3073
                                        }
3074
                                    }
62✔
3075
                                }
62✔
3076

3077
                                break;
1,191✔
3078
                            }
3079

3080
                            if ($inTernary === false
794✔
3081
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,191✔
3082
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,191✔
3083
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,191✔
3084
                            ) {
397✔
3085
                                // Found a nested arrow function that already has the closer set and is in
3086
                                // the same scope as us, so we can use its closer.
3087
                                break;
186✔
3088
                            }
3089

3090
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,191✔
3091
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,191✔
3092
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,191✔
3093
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,191✔
3094
                            ) {
397✔
3095
                                // We minus 1 here in case the closer can be shared with us.
3096
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
423✔
3097
                                continue;
423✔
3098
                            }
3099

3100
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,191✔
3101
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
741✔
3102
                                $lastEndToken = $scopeCloser;
741✔
3103
                                continue;
741✔
3104
                            }
3105

3106
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,191✔
3107
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
186✔
3108
                                $lastEndToken = $scopeCloser;
186✔
3109
                                continue;
186✔
3110
                            }
3111

3112
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,191✔
3113
                                $inTernary = true;
186✔
3114
                                continue;
186✔
3115
                            }
3116

3117
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,191✔
3118
                                if ($inTernary === false) {
186✔
3119
                                    break;
186✔
3120
                                }
3121

3122
                                $inTernary = false;
186✔
3123
                                continue;
186✔
3124
                            }
3125
                        }//end for
397✔
3126

3127
                        if ($scopeCloser !== $numTokens) {
1,191✔
3128
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,191✔
3129
                                $line = $this->tokens[$i]['line'];
×
3130
                                echo "\t=> token $i on line $line processed as arrow function".PHP_EOL;
×
3131
                                echo "\t\t* scope opener set to $arrow *".PHP_EOL;
×
3132
                                echo "\t\t* scope closer set to $scopeCloser *".PHP_EOL;
×
3133
                                echo "\t\t* parenthesis opener set to $x *".PHP_EOL;
×
3134
                                echo "\t\t* parenthesis closer set to $closer *".PHP_EOL;
×
3135
                            }
3136

3137
                            $this->tokens[$i]['code']            = T_FN;
1,191✔
3138
                            $this->tokens[$i]['type']            = 'T_FN';
1,191✔
3139
                            $this->tokens[$i]['scope_condition'] = $i;
1,191✔
3140
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,191✔
3141
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,191✔
3142
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,191✔
3143
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,191✔
3144
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,191✔
3145

3146
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,191✔
3147
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,191✔
3148

3149
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,191✔
3150
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,191✔
3151
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,191✔
3152
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,191✔
3153
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,191✔
3154
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,191✔
3155

3156
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,191✔
3157
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,191✔
3158
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,191✔
3159
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,191✔
3160

3161
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,191✔
3162
                                $line = $this->tokens[$arrow]['line'];
×
3163
                                echo "\t\t* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW".PHP_EOL;
×
3164
                            }
3165
                        }//end if
397✔
3166
                    }//end if
397✔
3167
                }//end if
397✔
3168

3169
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
3170
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,194✔
3171
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
3172
                        $line = $this->tokens[$i]['line'];
×
3173
                        echo "\t=> token $i on line $line is not an arrow function".PHP_EOL;
×
3174
                        echo "\t\t* token changed from T_FN to T_STRING".PHP_EOL;
×
3175
                    }
3176

3177
                    $this->tokens[$i]['code'] = T_STRING;
639✔
3178
                    $this->tokens[$i]['type'] = 'T_STRING';
824✔
3179
                }
213✔
3180
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,855✔
3181
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,180✔
3182
                    continue;
114✔
3183
                }
3184

3185
                // Unless there is a variable or a bracket before this token,
3186
                // it is the start of an array being defined using the short syntax.
3187
                $isShortArray = false;
1,180✔
3188
                $allowed      = [
406✔
3189
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,180✔
3190
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,180✔
3191
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,180✔
3192
                    T_VARIABLE                 => T_VARIABLE,
1,180✔
3193
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,180✔
3194
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,180✔
3195
                    T_STRING                   => T_STRING,
1,180✔
3196
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,180✔
3197
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,180✔
3198
                ];
774✔
3199
                $allowed     += Tokens::$magicConstants;
1,180✔
3200

3201
                for ($x = ($i - 1); $x >= 0; $x--) {
1,180✔
3202
                    // Allow for PHP 8.4 anon class dereferencing without wrapping parentheses.
3203
                    // Note: the T_CLASS token has not yet been retokenized to T_ANON_CLASS!
3204
                    if ($this->tokens[$x]['code'] === T_CLOSE_CURLY_BRACKET
1,180✔
3205
                        && isset($this->tokens[$x]['scope_condition']) === true
1,180✔
3206
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_CLASS
1,180✔
3207
                    ) {
368✔
3208
                        // Now, make sure it is an anonymous class and not a normal class.
3209
                        for ($y = ($this->tokens[$x]['scope_condition'] + 1); $y < $numTokens; $y++) {
114✔
3210
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
114✔
3211
                                break;
114✔
3212
                            }
3213
                        }
38✔
3214

3215
                        // Use the same check as used for the anon class retokenization.
3216
                        if ($this->tokens[$y]['code'] === T_OPEN_PARENTHESIS
114✔
3217
                            || $this->tokens[$y]['code'] === T_OPEN_CURLY_BRACKET
114✔
3218
                            || $this->tokens[$y]['code'] === T_EXTENDS
114✔
3219
                            || $this->tokens[$y]['code'] === T_IMPLEMENTS
114✔
3220
                        ) {
38✔
3221
                            break;
114✔
3222
                        }
3223
                    }
38✔
3224

3225
                    // If we hit a scope opener, the statement has ended
3226
                    // without finding anything, so it's probably an array
3227
                    // using PHP 7.1 short list syntax.
3228
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,180✔
3229
                        $isShortArray = true;
249✔
3230
                        break;
249✔
3231
                    }
3232

3233
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,180✔
3234
                        // Allow for control structures without braces.
3235
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,180✔
3236
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,180✔
3237
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
444✔
3238
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,180✔
3239
                        ) {
368✔
3240
                            $isShortArray = true;
1,180✔
3241
                        }
368✔
3242

3243
                        break;
1,180✔
3244
                    }
3245
                }//end for
368✔
3246

3247
                if ($isShortArray === true) {
1,180✔
3248
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,180✔
3249
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,180✔
3250

3251
                    $closer = $this->tokens[$i]['bracket_closer'];
1,180✔
3252
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,180✔
3253
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,180✔
3254
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,180✔
3255
                        $line = $this->tokens[$i]['line'];
×
3256
                        echo "\t* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY".PHP_EOL;
×
3257
                        $line = $this->tokens[$closer]['line'];
×
3258
                        echo "\t* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY".PHP_EOL;
×
3259
                    }
3260
                }
368✔
3261

3262
                continue;
1,180✔
3263
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,855✔
3264
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
795✔
3265
                    // Not a match expression after all.
3266
                    $this->tokens[$i]['code'] = T_STRING;
102✔
3267
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
3268

3269
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3270
                        echo "\t\t* token $i changed from T_MATCH to T_STRING".PHP_EOL;
×
3271
                    }
3272

3273
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
3274
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
3275
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
3276
                        unset(
34✔
3277
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
3278
                            $this->tokens[$closer]['parenthesis_owner']
102✔
3279
                        );
34✔
3280
                        unset(
34✔
3281
                            $this->tokens[$i]['parenthesis_opener'],
102✔
3282
                            $this->tokens[$i]['parenthesis_closer'],
102✔
3283
                            $this->tokens[$i]['parenthesis_owner']
102✔
3284
                        );
34✔
3285

3286
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3287
                            echo "\t\t* cleaned parenthesis of token $i *".PHP_EOL;
34✔
3288
                        }
3289
                    }
34✔
3290
                } else {
34✔
3291
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3292
                    $searchFor  = [
265✔
3293
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
795✔
3294
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
795✔
3295
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
795✔
3296
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
795✔
3297
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
795✔
3298
                    ];
530✔
3299
                    $searchFor += Tokens::$scopeOpeners;
795✔
3300

3301
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
795✔
3302
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
795✔
3303
                            continue;
795✔
3304
                        }
3305

3306
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
477✔
3307
                            $x = $this->tokens[$x]['scope_closer'];
291✔
3308
                            continue;
291✔
3309
                        }
3310

3311
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
477✔
3312
                            $x = $this->tokens[$x]['parenthesis_closer'];
477✔
3313
                            continue;
477✔
3314
                        }
3315

3316
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
477✔
3317
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
3318
                            continue;
291✔
3319
                        }
3320

3321
                        // This must be a double arrow, but make sure anyhow.
3322
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
477✔
3323
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
477✔
3324
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
477✔
3325

3326
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
477✔
3327
                                echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
3328
                            }
3329
                        }
159✔
3330
                    }//end for
159✔
3331
                }//end if
3332

3333
                continue;
795✔
3334
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,855✔
3335
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,855✔
3336
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,855✔
3337
            ) {
593✔
3338
                if ($lastSeenTypeToken < $i) {
1,852✔
3339
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3340
                    // No need to do it again.
3341
                    continue;
906✔
3342
                }
3343

3344
                /*
3345
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3346
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3347
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3348

3349
                    All type related tokens will be converted in one go as soon as this section is hit.
3350
                */
3351

3352
                $allowed = [
630✔
3353
                    T_STRING       => T_STRING,
1,852✔
3354
                    T_CALLABLE     => T_CALLABLE,
1,852✔
3355
                    T_SELF         => T_SELF,
1,852✔
3356
                    T_PARENT       => T_PARENT,
1,852✔
3357
                    T_STATIC       => T_STATIC,
1,852✔
3358
                    T_FALSE        => T_FALSE,
1,852✔
3359
                    T_TRUE         => T_TRUE,
1,852✔
3360
                    T_NULL         => T_NULL,
1,852✔
3361
                    T_NAMESPACE    => T_NAMESPACE,
1,852✔
3362
                    T_NS_SEPARATOR => T_NS_SEPARATOR,
1,852✔
3363
                ];
1,222✔
3364

3365
                $suspectedType       = null;
1,852✔
3366
                $typeTokenCountAfter = 0;
1,852✔
3367

3368
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,852✔
3369
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,852✔
3370
                        continue;
1,852✔
3371
                    }
3372

3373
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,852✔
3374
                        ++$typeTokenCountAfter;
1,408✔
3375
                        continue;
1,408✔
3376
                    }
3377

3378
                    if (($typeTokenCountAfter > 0
1,260✔
3379
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,852✔
3380
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,852✔
3381
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,848✔
3382
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,848✔
3383
                    ) {
592✔
3384
                        // Skip past reference and variadic indicators for parameter types.
3385
                        continue;
969✔
3386
                    }
3387

3388
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,852✔
3389
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3390
                        $suspectedType = 'property or parameter';
1,269✔
3391
                        break;
1,269✔
3392
                    }
3393

3394
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,852✔
3395
                        // Possible arrow function.
3396
                        $suspectedType = 'return';
1,191✔
3397
                        break;
1,191✔
3398
                    }
3399

3400
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,852✔
3401
                        // Possible abstract method or interface method.
3402
                        $suspectedType = 'return';
1,663✔
3403
                        break;
1,663✔
3404
                    }
3405

3406
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,852✔
3407
                        && isset($this->tokens[$x]['scope_condition']) === true
1,852✔
3408
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,852✔
3409
                    ) {
592✔
3410
                        $suspectedType = 'return';
1,587✔
3411
                        break;
1,587✔
3412
                    }
3413

3414
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,738✔
3415
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3416
                        $suspectedType = 'constant';
1,078✔
3417
                        break;
1,078✔
3418
                    }
3419

3420
                    break;
1,738✔
3421
                }//end for
3422

3423
                if (($typeTokenCountAfter === 0
1,260✔
3424
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,852✔
3425
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,852✔
3426
                    || isset($suspectedType) === false
1,260✔
3427
                ) {
592✔
3428
                    // Definitely not a union, intersection or DNF type, move on.
3429
                    continue;
1,852✔
3430
                }
3431

3432
                if ($suspectedType === 'property or parameter') {
1,813✔
3433
                    unset($allowed[T_STATIC]);
1,269✔
3434
                }
423✔
3435

3436
                $typeTokenCountBefore = 0;
1,813✔
3437
                $typeOperators        = [$i];
1,813✔
3438
                $parenthesesCount     = 0;
1,813✔
3439
                $confirmed            = false;
1,813✔
3440
                $maybeNullable        = null;
1,813✔
3441

3442
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,813✔
3443
                    ++$parenthesesCount;
1,813✔
3444
                }
579✔
3445

3446
                for ($x = ($i - 1); $x >= 0; $x--) {
1,813✔
3447
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,813✔
3448
                        continue;
1,510✔
3449
                    }
3450

3451
                    if ($suspectedType === 'property or parameter'
1,234✔
3452
                        && $this->tokens[$x]['code'] === T_STRING
1,813✔
3453
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,813✔
3454
                    ) {
579✔
3455
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3456
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3457
                        $this->tokens[$x]['code'] = T_STATIC;
318✔
3458
                        $this->tokens[$x]['type'] = 'T_STATIC';
318✔
3459

3460
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
318✔
3461
                            $line = $this->tokens[$x]['line'];
×
3462
                            echo "\t* token $x on line $line changed back from T_STRING to T_STATIC".PHP_EOL;
×
3463
                        }
3464
                    }
106✔
3465

3466
                    if ($suspectedType === 'property or parameter'
1,234✔
3467
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,813✔
3468
                    ) {
579✔
3469
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3470
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3471
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,269✔
3472
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,269✔
3473
                        ) {
423✔
3474
                            $confirmed = true;
1,155✔
3475
                            break;
1,155✔
3476
                        } else {
3477
                            // This may still be an arrow function which hasn't been handled yet.
3478
                            for ($y = ($x - 1); $y > 0; $y--) {
1,269✔
3479
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
1,269✔
3480
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,269✔
3481
                                ) {
423✔
3482
                                    // Non-whitespace content.
3483
                                    break;
1,269✔
3484
                                }
3485
                            }
423✔
3486

3487
                            if ($this->tokens[$y]['code'] === T_FN) {
1,269✔
3488
                                $confirmed = true;
954✔
3489
                                break;
954✔
3490
                            }
3491
                        }
3492
                    }//end if
423✔
3493

3494
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,813✔
3495
                        ++$typeTokenCountBefore;
1,510✔
3496
                        continue;
1,510✔
3497
                    }
3498

3499
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3500
                    if (($typeTokenCountBefore > 0
1,234✔
3501
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,813✔
3502
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,712✔
3503
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,712✔
3504
                    ) {
579✔
3505
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
907✔
3506
                            $maybeNullable = $x;
318✔
3507
                        }
106✔
3508

3509
                        continue;
907✔
3510
                    }
3511

3512
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,813✔
3513
                        $typeOperators[] = $x;
1,294✔
3514
                        continue;
1,294✔
3515
                    }
3516

3517
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,813✔
3518
                        ++$parenthesesCount;
1,510✔
3519
                        $typeOperators[] = $x;
1,510✔
3520
                        continue;
1,510✔
3521
                    }
3522

3523
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,813✔
3524
                        // Make sure this is the colon for a return type.
3525
                        for ($y = ($x - 1); $y > 0; $y--) {
990✔
3526
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
990✔
3527
                                break;
990✔
3528
                            }
3529
                        }
330✔
3530

3531
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
990✔
3532
                            // Definitely not a union, intersection or DNF return type, move on.
3533
                            continue 2;
318✔
3534
                        }
3535

3536
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
990✔
3537
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
954✔
3538
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
530✔
3539
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
742✔
3540
                            ) {
318✔
3541
                                $confirmed = true;
954✔
3542
                            }
318✔
3543

3544
                            break;
954✔
3545
                        }
3546

3547
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3548
                        // Closure use tokens won't be parentheses owners until PHPCS 4.0.
3549
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
990✔
3550
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
954✔
3551
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
954✔
3552
                                    break;
954✔
3553
                                }
3554
                            }
256✔
3555

3556
                            if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
954✔
3557
                                $confirmed = true;
954✔
3558
                            }
318✔
3559
                        }
318✔
3560

3561
                        break;
990✔
3562
                    }//end if
3563

3564
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,813✔
3565
                        $confirmed = true;
943✔
3566
                        break;
943✔
3567
                    }
3568

3569
                    if ($suspectedType === 'property or parameter'
1,234✔
3570
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,557✔
3571
                        || $this->tokens[$x]['code'] === T_VAR
1,289✔
3572
                        || $this->tokens[$x]['code'] === T_STATIC
1,239✔
3573
                        || $this->tokens[$x]['code'] === T_READONLY
1,239✔
3574
                        || $this->tokens[$x]['code'] === T_FINAL
1,184✔
3575
                        || $this->tokens[$x]['code'] === T_ABSTRACT)
1,490✔
3576
                    ) {
579✔
3577
                        // This will also confirm constructor property promotion parameters, but that's fine.
3578
                        $confirmed = true;
1,083✔
3579
                    }
361✔
3580

3581
                    break;
1,813✔
3582
                }//end for
3583

3584
                // Remember the last token we examined as part of the (non-)"type declaration".
3585
                $lastSeenTypeToken = $x;
1,813✔
3586

3587
                if ($confirmed === false
1,234✔
3588
                    && $suspectedType === 'property or parameter'
1,813✔
3589
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,813✔
3590
                ) {
579✔
3591
                    $parens = $this->tokens[$i]['nested_parenthesis'];
768✔
3592
                    $last   = end($parens);
768✔
3593

3594
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
768✔
3595
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
768✔
3596
                    ) {
256✔
3597
                        $confirmed = true;
768✔
3598
                    } else {
256✔
3599
                        // No parenthesis owner set, this may be an arrow function which has not yet
3600
                        // had additional processing done.
3601
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
234✔
3602
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
234✔
3603
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
234✔
3604
                                    continue;
234✔
3605
                                }
3606

3607
                                break;
234✔
3608
                            }
3609

3610
                            if ($this->tokens[$x]['code'] === T_FN) {
234✔
3611
                                for (--$x; $x >= 0; $x--) {
×
3612
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
3613
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3614
                                    ) {
3615
                                        continue;
×
3616
                                    }
3617

3618
                                    break;
×
3619
                                }
3620

3621
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3622
                                    $confirmed = true;
×
3623
                                }
3624
                            }
3625
                        }//end if
78✔
3626
                    }//end if
3627

3628
                    unset($parens, $last);
768✔
3629
                }//end if
256✔
3630

3631
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,813✔
3632
                    // Not a (valid) union, intersection or DNF type after all, move on.
3633
                    continue;
1,699✔
3634
                }
3635

3636
                foreach ($typeOperators as $x) {
1,372✔
3637
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,372✔
3638
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,258✔
3639
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,258✔
3640

3641
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,258✔
3642
                            $line = $this->tokens[$x]['line'];
×
3643
                            echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
432✔
3644
                        }
3645
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,372✔
3646
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,258✔
3647
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,258✔
3648

3649
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,258✔
3650
                            $line = $this->tokens[$x]['line'];
×
3651
                            echo "\t* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION".PHP_EOL;
432✔
3652
                        }
3653
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,372✔
3654
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,372✔
3655
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,372✔
3656

3657
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,372✔
3658
                            $line = $this->tokens[$x]['line'];
×
3659
                            echo "\t* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS".PHP_EOL;
470✔
3660
                        }
3661
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,372✔
3662
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,372✔
3663
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,372✔
3664

3665
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,372✔
3666
                            $line = $this->tokens[$x]['line'];
×
3667
                            echo "\t* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS".PHP_EOL;
×
3668
                        }
3669
                    }//end if
432✔
3670
                }//end foreach
432✔
3671

3672
                if (isset($maybeNullable) === true) {
1,372✔
3673
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
318✔
3674
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
318✔
3675

3676
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
318✔
3677
                        $line = $this->tokens[$maybeNullable]['line'];
×
3678
                        echo "\t* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE".PHP_EOL;
×
3679
                    }
3680
                }
106✔
3681

3682
                continue;
1,372✔
3683
            } else if ($this->tokens[$i]['code'] === T_STATIC) {
1,855✔
3684
                for ($x = ($i - 1); $x > 0; $x--) {
1,363✔
3685
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,363✔
3686
                        break;
1,363✔
3687
                    }
3688
                }
429✔
3689

3690
                if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
1,363✔
3691
                    $this->tokens[$i]['code'] = T_STRING;
×
3692
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3693

3694
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3695
                        $line = $this->tokens[$i]['line'];
×
3696
                        echo "\t* token $i on line $line changed from T_STATIC to T_STRING".PHP_EOL;
×
3697
                    }
3698
                }
3699

3700
                continue;
1,363✔
3701
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,855✔
3702
                || $this->tokens[$i]['code'] === T_FALSE
1,855✔
3703
                || $this->tokens[$i]['code'] === T_NULL
1,855✔
3704
                || $this->tokens[$i]['code'] === T_EXIT
1,855✔
3705
            ) {
593✔
3706
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,657✔
3707
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,657✔
3708
                        // Non-whitespace content.
3709
                        break;
1,657✔
3710
                    }
3711
                }
466✔
3712

3713
                if ($x !== $numTokens
1,130✔
3714
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,657✔
3715
                ) {
527✔
3716
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3717
                        $line = $this->tokens[$i]['line'];
×
3718
                        $type = $this->tokens[$i]['type'];
×
3719
                        echo "\t* token $i on line $line changed from $type to T_STRING".PHP_EOL;
×
3720
                    }
3721

3722
                    $this->tokens[$i]['code'] = T_STRING;
×
3723
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3724
                }
3725
            }//end if
527✔
3726

3727
            if (($this->tokens[$i]['code'] !== T_CASE
1,855✔
3728
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,855✔
3729
                || isset($this->tokens[$i]['scope_opener']) === false
1,420✔
3730
            ) {
593✔
3731
                // Only interested in CASE and DEFAULT statements from here on in.
3732
                continue;
1,855✔
3733
            }
3734

3735
            $scopeOpener = $this->tokens[$i]['scope_opener'];
474✔
3736
            $scopeCloser = $this->tokens[$i]['scope_closer'];
474✔
3737

3738
            // If the first char after the opener is a curly brace
3739
            // and that brace has been ignored, it is actually
3740
            // opening this case statement and the opener and closer are
3741
            // probably set incorrectly.
3742
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
474✔
3743
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
474✔
3744
                    // Non-whitespace content.
3745
                    break;
474✔
3746
                }
3747
            }
158✔
3748

3749
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
474✔
3750
                // Special case for multiple CASE statements that share the same
3751
                // closer. Because we are going backwards through the file, this next
3752
                // CASE statement is already fixed, so just use its closer and don't
3753
                // worry about fixing anything.
3754
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3755
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3756
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3757
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3758
                    $newType = $this->tokens[$newCloser]['type'];
×
3759
                    $line    = $this->tokens[$i]['line'];
×
3760
                    echo "\t* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3761
                }
3762

3763
                continue;
×
3764
            }
3765

3766
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
474✔
3767
                || isset($this->tokens[$x]['scope_condition']) === true
474✔
3768
            ) {
158✔
3769
                // Not a CASE/DEFAULT with a curly brace opener.
3770
                continue;
474✔
3771
            }
3772

3773
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3774
            // not whatever it already is. The opener needs to be the opening curly
3775
            // brace so everything matches up.
3776
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3777
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3778
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3779
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3780
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3781
            }
18✔
3782

3783
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3784
                $line      = $this->tokens[$i]['line'];
×
3785
                $tokenType = $this->tokens[$i]['type'];
×
3786

3787
                $oldType = $this->tokens[$scopeOpener]['type'];
×
3788
                $newType = $this->tokens[$x]['type'];
×
3789
                echo "\t* token $i ($tokenType) on line $line opener changed from $scopeOpener ($oldType) to $x ($newType)".PHP_EOL;
×
3790

3791
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3792
                $newType = $this->tokens[$newCloser]['type'];
×
3793
                echo "\t* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3794
            }
3795

3796
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3797
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3798
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3799
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3800
            }
18✔
3801

3802
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3803
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3804
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3805
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3806
            } else {
18✔
3807
                // We were using a shared closer. All tokens that were
3808
                // sharing this closer with us, except for the scope condition
3809
                // and it's opener, need to now point to the new closer.
3810
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3811
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3812
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3813
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3814
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3815
                    ) {
3816
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3817

3818
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3819
                            $line      = $this->tokens[$y]['line'];
×
3820
                            $tokenType = $this->tokens[$y]['type'];
×
3821
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3822
                            $newType   = $this->tokens[$newCloser]['type'];
×
3823
                            echo "\t\t* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3824
                        }
3825
                    }
3826
                }
3827
            }//end if
3828

3829
            unset($this->tokens[$x]['bracket_opener']);
54✔
3830
            unset($this->tokens[$x]['bracket_closer']);
54✔
3831
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3832
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3833
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3834

3835
            // Now fix up all the tokens that think they are
3836
            // inside the CASE/DEFAULT statement when they are really outside.
3837
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3838
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3839
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3840
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3841
                        unset($this->tokens[$x]['conditions'][$num]);
×
3842

3843
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3844
                            $type     = $this->tokens[$x]['type'];
×
3845
                            $oldConds = '';
×
3846
                            foreach ($oldConditions as $condition) {
×
3847
                                $oldConds .= Tokens::tokenName($condition).',';
×
3848
                            }
3849

3850
                            $oldConds = rtrim($oldConds, ',');
×
3851

3852
                            $newConds = '';
×
3853
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3854
                                $newConds .= Tokens::tokenName($condition).',';
×
3855
                            }
3856

3857
                            $newConds = rtrim($newConds, ',');
×
3858

3859
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
3860
                            echo "\t\t\t=> conditions changed from $oldConds to $newConds".PHP_EOL;
×
3861
                        }
3862

3863
                        break;
×
3864
                    }//end if
3865
                }//end foreach
3866
            }//end for
3867
        }//end for
18✔
3868

3869
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,855✔
3870
            echo "\t*** END ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
3871
        }
3872

3873
    }//end processAdditional()
1,224✔
3874

3875

3876
    /**
3877
     * Takes a token produced from <code>token_get_all()</code> and produces a
3878
     * more uniform token.
3879
     *
3880
     * @param string|array $token The token to convert.
3881
     *
3882
     * @return array The new token.
3883
     */
3884
    public static function standardiseToken($token)
3✔
3885
    {
3886
        if (isset($token[1]) === false) {
3✔
3887
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3✔
3888
                return self::$resolveTokenCache[$token[0]];
1✔
3889
            }
3890
        } else {
1✔
3891
            $cacheKey = null;
3✔
3892
            if ($token[0] === T_STRING) {
3✔
3893
                $cacheKey = strtolower($token[1]);
3✔
3894
            } else if ($token[0] !== T_CURLY_OPEN) {
3✔
3895
                $cacheKey = $token[0];
3✔
3896
            }
1✔
3897

3898
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3✔
3899
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3900
                $newToken['content'] = $token[1];
×
3901
                return $newToken;
×
3902
            }
3903
        }
3904

3905
        if (isset($token[1]) === false) {
3✔
3906
            return self::resolveSimpleToken($token[0]);
3✔
3907
        }
3908

3909
        if ($token[0] === T_STRING) {
3✔
3910
            switch ($cacheKey) {
1✔
3911
            case 'false':
3✔
3912
                $newToken['type'] = 'T_FALSE';
3✔
3913
                break;
3✔
3914
            case 'true':
3✔
3915
                $newToken['type'] = 'T_TRUE';
3✔
3916
                break;
3✔
3917
            case 'null':
3✔
3918
                $newToken['type'] = 'T_NULL';
3✔
3919
                break;
3✔
3920
            case 'self':
3✔
3921
                $newToken['type'] = 'T_SELF';
3✔
3922
                break;
3✔
3923
            case 'parent':
3✔
3924
                $newToken['type'] = 'T_PARENT';
3✔
3925
                break;
3✔
3926
            default:
1✔
3927
                $newToken['type'] = 'T_STRING';
3✔
3928
                break;
3✔
3929
            }
1✔
3930

3931
            $newToken['code'] = constant($newToken['type']);
3✔
3932

3933
            self::$resolveTokenCache[$cacheKey] = $newToken;
3✔
3934
        } else if ($token[0] === T_CURLY_OPEN) {
3✔
3935
            $newToken = [
3936
                'code' => T_OPEN_CURLY_BRACKET,
×
3937
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3938
            ];
3939
        } else {
3940
            $newToken = [
1✔
3941
                'code' => $token[0],
3✔
3942
                'type' => Tokens::tokenName($token[0]),
3✔
3943
            ];
2✔
3944

3945
            self::$resolveTokenCache[$token[0]] = $newToken;
3✔
3946
        }//end if
3947

3948
        $newToken['content'] = $token[1];
3✔
3949
        return $newToken;
3✔
3950

3951
    }//end standardiseToken()
3952

3953

3954
    /**
3955
     * Converts simple tokens into a format that conforms to complex tokens
3956
     * produced by token_get_all().
3957
     *
3958
     * Simple tokens are tokens that are not in array form when produced from
3959
     * token_get_all().
3960
     *
3961
     * @param string $token The simple token to convert.
3962
     *
3963
     * @return array The new token in array format.
3964
     */
3965
    public static function resolveSimpleToken($token)
3✔
3966
    {
3967
        $newToken = [];
3✔
3968

3969
        switch ($token) {
1✔
3970
        case '{':
3✔
3971
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
3✔
3972
            break;
3✔
3973
        case '}':
3✔
3974
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
3✔
3975
            break;
3✔
3976
        case '[':
3✔
3977
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
3✔
3978
            break;
3✔
3979
        case ']':
3✔
3980
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
3✔
3981
            break;
3✔
3982
        case '(':
3✔
3983
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
3✔
3984
            break;
3✔
3985
        case ')':
3✔
3986
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
3✔
3987
            break;
3✔
3988
        case ':':
3✔
3989
            $newToken['type'] = 'T_COLON';
3✔
3990
            break;
3✔
3991
        case '.':
3✔
3992
            $newToken['type'] = 'T_STRING_CONCAT';
3✔
3993
            break;
3✔
3994
        case ';':
3✔
3995
            $newToken['type'] = 'T_SEMICOLON';
3✔
3996
            break;
3✔
3997
        case '=':
3✔
3998
            $newToken['type'] = 'T_EQUAL';
3✔
3999
            break;
3✔
4000
        case '*':
3✔
4001
            $newToken['type'] = 'T_MULTIPLY';
3✔
4002
            break;
3✔
4003
        case '/':
3✔
4004
            $newToken['type'] = 'T_DIVIDE';
3✔
4005
            break;
3✔
4006
        case '+':
3✔
4007
            $newToken['type'] = 'T_PLUS';
3✔
4008
            break;
3✔
4009
        case '-':
3✔
4010
            $newToken['type'] = 'T_MINUS';
3✔
4011
            break;
3✔
4012
        case '%':
3✔
4013
            $newToken['type'] = 'T_MODULUS';
3✔
4014
            break;
3✔
4015
        case '^':
3✔
4016
            $newToken['type'] = 'T_BITWISE_XOR';
3✔
4017
            break;
3✔
4018
        case '&':
3✔
4019
            $newToken['type'] = 'T_BITWISE_AND';
2✔
4020
            break;
2✔
4021
        case '|':
3✔
4022
            $newToken['type'] = 'T_BITWISE_OR';
3✔
4023
            break;
3✔
4024
        case '~':
3✔
4025
            $newToken['type'] = 'T_BITWISE_NOT';
3✔
4026
            break;
3✔
4027
        case '<':
3✔
4028
            $newToken['type'] = 'T_LESS_THAN';
3✔
4029
            break;
3✔
4030
        case '>':
3✔
4031
            $newToken['type'] = 'T_GREATER_THAN';
3✔
4032
            break;
3✔
4033
        case '!':
3✔
4034
            $newToken['type'] = 'T_BOOLEAN_NOT';
3✔
4035
            break;
3✔
4036
        case ',':
3✔
4037
            $newToken['type'] = 'T_COMMA';
3✔
4038
            break;
3✔
4039
        case '@':
3✔
4040
            $newToken['type'] = 'T_ASPERAND';
3✔
4041
            break;
3✔
4042
        case '$':
3✔
4043
            $newToken['type'] = 'T_DOLLAR';
3✔
4044
            break;
3✔
4045
        case '`':
3✔
4046
            $newToken['type'] = 'T_BACKTICK';
3✔
4047
            break;
3✔
4048
        default:
4049
            $newToken['type'] = 'T_NONE';
×
4050
            break;
×
4051
        }//end switch
4052

4053
        $newToken['code']    = constant($newToken['type']);
3✔
4054
        $newToken['content'] = $token;
3✔
4055

4056
        self::$resolveTokenCache[$token] = $newToken;
3✔
4057
        return $newToken;
3✔
4058

4059
    }//end resolveSimpleToken()
4060

4061

4062
    /**
4063
     * Finds a "closer" token (closing parenthesis or square bracket for example)
4064
     * Handle parenthesis balancing while searching for closing token
4065
     *
4066
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
4067
     * @param int             $start        The starting position.
4068
     * @param string|string[] $openerTokens The opening character.
4069
     * @param string          $closerChar   The closing character.
4070
     *
4071
     * @return int|null The position of the closing token, if found. NULL otherwise.
4072
     */
4073
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
4074
    {
4075
        $numTokens    = count($tokens);
51✔
4076
        $stack        = [0];
51✔
4077
        $closer       = null;
51✔
4078
        $openerTokens = (array) $openerTokens;
51✔
4079

4080
        for ($x = $start; $x < $numTokens; $x++) {
51✔
4081
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
4082
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
4083
            ) {
17✔
4084
                $stack[] = $x;
51✔
4085
            } else if ($tokens[$x] === $closerChar) {
51✔
4086
                array_pop($stack);
51✔
4087
                if (empty($stack) === true) {
51✔
4088
                    $closer = $x;
51✔
4089
                    break;
51✔
4090
                }
4091
            }
17✔
4092
        }
17✔
4093

4094
        return $closer;
51✔
4095

4096
    }//end findCloser()
4097

4098

4099
    /**
4100
     * PHP 8 attributes parser for PHP < 8
4101
     * Handles single-line and multiline attributes.
4102
     *
4103
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
4104
     * @param int   $stackPtr The current position in token array.
4105
     *
4106
     * @return array|null The array of parsed attribute tokens
4107
     */
4108
    private function parsePhpAttribute(array &$tokens, $stackPtr)
38✔
4109
    {
4110

4111
        $token = $tokens[$stackPtr];
38✔
4112

4113
        $commentBody = substr($token[1], 2);
38✔
4114
        $subTokens   = @token_get_all('<?php '.$commentBody);
38✔
4115

4116
        foreach ($subTokens as $i => $subToken) {
38✔
4117
            if (is_array($subToken) === true
38✔
4118
                && $subToken[0] === T_COMMENT
38✔
4119
                && strpos($subToken[1], '#[') === 0
38✔
4120
            ) {
19✔
4121
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
38✔
4122
                if ($reparsed !== null) {
38✔
4123
                    array_splice($subTokens, $i, 1, $reparsed);
38✔
4124
                } else {
19✔
4125
                    $subToken[0] = T_ATTRIBUTE;
×
4126
                }
4127
            }
19✔
4128
        }
19✔
4129

4130
        array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
38✔
4131

4132
        // Go looking for the close bracket.
4133
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
38✔
4134
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
38✔
4135
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
38✔
4136
                if (is_array($token) === true) {
38✔
4137
                    $commentBody .= $token[1];
38✔
4138
                } else {
19✔
4139
                    $commentBody .= $token;
38✔
4140
                }
4141
            }
19✔
4142

4143
            $subTokens = @token_get_all('<?php '.$commentBody);
38✔
4144
            array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
38✔
4145

4146
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
38✔
4147
            if ($bracketCloser !== null) {
38✔
4148
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
38✔
4149
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
38✔
4150
            }
19✔
4151
        }
19✔
4152

4153
        if ($bracketCloser === null) {
38✔
4154
            return null;
38✔
4155
        }
4156

4157
        return $subTokens;
38✔
4158

4159
    }//end parsePhpAttribute()
4160

4161

4162
    /**
4163
     * Creates a map for the attributes tokens that surround other tokens.
4164
     *
4165
     * @return void
4166
     */
4167
    private function createAttributesNestingMap()
3✔
4168
    {
4169
        $map = [];
3✔
4170
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
4171
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
4172
                && $i === $this->tokens[$i]['attribute_opener']
3✔
4173
            ) {
1✔
4174
                if (empty($map) === false) {
3✔
4175
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4176
                }
1✔
4177

4178
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
4179
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
4180
                        = $this->tokens[$i]['attribute_closer'];
3✔
4181
                }
1✔
4182
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
4183
                && $i === $this->tokens[$i]['attribute_closer']
3✔
4184
            ) {
1✔
4185
                array_pop($map);
3✔
4186
                if (empty($map) === false) {
3✔
4187
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4188
                }
1✔
4189
            } else {
1✔
4190
                if (empty($map) === false) {
3✔
4191
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4192
                }
1✔
4193
            }//end if
4194
        }//end for
1✔
4195

4196
    }//end createAttributesNestingMap()
2✔
4197

4198

4199
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc