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

PHPCSStandards / PHP_CodeSniffer / 14519085527

17 Apr 2025 03:20PM UTC coverage: 78.008% (+0.01%) from 77.995%
14519085527

push

github

web-flow
Merge pull request #1015 from PHPCSStandards/phpcs-4.0/feature/593-tokenizer-php-open-tag-consistency

Tokenizer/PHP: change tokenization of long PHP open tags

42 of 45 new or added lines in 6 files covered. (93.33%)

1 existing line in 1 file now uncovered.

19456 of 24941 relevant lines covered (78.01%)

82.03 hits per line

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

86.32
/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
use PHP_CodeSniffer\Util\Writers\StatusWriter;
15

16
class PHP extends Tokenizer
17
{
18

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

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

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

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

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

506

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

527
        $tokens      = @token_get_all($string);
3,072✔
528
        $finalTokens = [];
3,072✔
529

530
        $newStackPtr       = 0;
3,072✔
531
        $numTokens         = count($tokens);
3,072✔
532
        $lastNotEmptyToken = 0;
3,072✔
533

534
        $insideInlineIf         = [];
3,072✔
535
        $insideUseGroup         = false;
3,072✔
536
        $insideConstDeclaration = false;
3,072✔
537

538
        $commentTokenizer = new Comment();
3,072✔
539

540
        for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
3,072✔
541
            // Special case for tokens we have needed to blank out.
542
            if ($tokens[$stackPtr] === null) {
3,072✔
543
                continue;
1,112✔
544
            }
545

546
            $token        = (array) $tokens[$stackPtr];
3,072✔
547
            $tokenIsArray = isset($token[1]);
3,072✔
548

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

559
                $statusMessage = 'Process token ';
×
560
                if ($tokenIsArray === true) {
×
561
                    $statusMessage .= "[$stackPtr]";
×
562
                } else {
563
                    $statusMessage .= " $stackPtr ";
×
564
                }
565

566
                $statusMessage .= ": $type => $content";
×
567
                StatusWriter::write($statusMessage, 1, 0);
×
568
            }//end if
569

570
            if ($newStackPtr > 0
3,072✔
571
                && isset(Tokens::$emptyTokens[$finalTokens[($newStackPtr - 1)]['code']]) === false
3,072✔
572
            ) {
573
                $lastNotEmptyToken = ($newStackPtr - 1);
3,072✔
574
            }
575

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

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

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

607
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,072✔
608
                StatusWriter::writeNewline();
×
609
            }
610

611
            /*
612
                Tokenize context sensitive keyword as string when it should be string.
613
            */
614

615
            if ($tokenIsArray === true
3,072✔
616
                && isset(Tokens::$contextSensitiveKeywords[$token[0]]) === true
3,072✔
617
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,972✔
618
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
2,972✔
619
                || $insideConstDeclaration === true)
3,072✔
620
            ) {
621
                if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,384✔
622
                    $preserveKeyword = false;
2,384✔
623

624
                    // `new class`, and `new static` should be preserved.
625
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,384✔
626
                        && ($token[0] === T_CLASS
2,130✔
627
                        || $token[0] === T_STATIC)
2,384✔
628
                    ) {
629
                        $preserveKeyword = true;
1,521✔
630
                    }
631

632
                    // `new readonly class` should be preserved.
633
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,384✔
634
                        && strtolower($token[1]) === 'readonly'
2,384✔
635
                    ) {
636
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
340✔
637
                            if (is_array($tokens[$i]) === false
340✔
638
                                || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
340✔
639
                            ) {
640
                                break;
340✔
641
                            }
642
                        }
643

644
                        if (is_array($tokens[$i]) === true && $tokens[$i][0] === T_CLASS) {
340✔
645
                            $preserveKeyword = true;
340✔
646
                        }
647
                    }
648

649
                    // `new class extends` `new class implements` should be preserved
650
                    if (($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS)
2,384✔
651
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CLASS
2,384✔
652
                    ) {
653
                        $preserveKeyword = true;
510✔
654
                    }
655

656
                    // `namespace\` should be preserved
657
                    if ($token[0] === T_NAMESPACE) {
2,384✔
658
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
618✔
659
                            if (is_array($tokens[$i]) === false) {
618✔
660
                                break;
510✔
661
                            }
662

663
                            if (isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true) {
618✔
664
                                continue;
510✔
665
                            }
666

667
                            if ($tokens[$i][0] === T_NS_SEPARATOR) {
278✔
668
                                $preserveKeyword = true;
278✔
669
                            }
670

671
                            break;
278✔
672
                        }
673
                    }
674
                }//end if
675

676
                // Types in typed constants should not be touched, but the constant name should be.
677
                if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,384✔
678
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,384✔
679
                    || $insideConstDeclaration === true
2,384✔
680
                ) {
681
                    $preserveKeyword = true;
2,243✔
682

683
                    // Find the next non-empty token.
684
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,243✔
685
                        if (is_array($tokens[$i]) === true
2,243✔
686
                            && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,243✔
687
                        ) {
688
                            continue;
1,604✔
689
                        }
690

691
                        break;
2,243✔
692
                    }
693

694
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
2,243✔
695
                        $preserveKeyword        = false;
800✔
696
                        $insideConstDeclaration = false;
800✔
697
                    }
698
                }//end if
699

700
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,384✔
701
                    $preserveKeyword = true;
627✔
702

703
                    for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
627✔
704
                        if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
627✔
705
                            continue;
564✔
706
                        }
707

708
                        if ($finalTokens[$i]['code'] === T_FUNCTION) {
627✔
709
                            $preserveKeyword = false;
564✔
710
                        }
711

712
                        break;
627✔
713
                    }
714
                }
715

716
                if ($preserveKeyword === false) {
2,384✔
717
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,015✔
718
                        $type = Tokens::tokenName($token[0]);
×
719
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
720
                    }
721

722
                    $finalTokens[$newStackPtr] = [
1,015✔
723
                        'code'    => T_STRING,
1,015✔
724
                        'type'    => 'T_STRING',
1,015✔
725
                        'content' => $token[1],
1,015✔
726
                    ];
706✔
727

728
                    $newStackPtr++;
1,015✔
729
                    continue;
1,015✔
730
                }
731
            }//end if
732

733
            /*
734
                Mark the start of a constant declaration to allow for handling keyword to T_STRING
735
                convertion for constant names using reserved keywords.
736
            */
737

738
            if ($tokenIsArray === true && $token[0] === T_CONST) {
3,072✔
739
                $insideConstDeclaration = true;
2,433✔
740
            }
741

742
            /*
743
                Close an open "inside constant declaration" marker when no keyword conversion was needed.
744
            */
745

746
            if ($insideConstDeclaration === true
3,072✔
747
                && $tokenIsArray === false
3,072✔
748
                && ($token[0] === '=' || $token[0] === ';')
3,072✔
749
            ) {
750
                $insideConstDeclaration = false;
944✔
751
            }
752

753
            /*
754
                Special case for `static` used as a function name, i.e. `static()`.
755

756
                Note: this may incorrectly change the static keyword directly before a DNF property type.
757
                If so, this will be caught and corrected for in the additional processing.
758
            */
759

760
            if ($tokenIsArray === true
3,072✔
761
                && $token[0] === T_STATIC
3,072✔
762
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
3,072✔
763
            ) {
764
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,052✔
765
                    if (is_array($tokens[$i]) === true
2,052✔
766
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,052✔
767
                    ) {
768
                        continue;
1,413✔
769
                    }
770

771
                    if ($tokens[$i][0] === '(') {
2,052✔
772
                        $finalTokens[$newStackPtr] = [
510✔
773
                            'code'    => T_STRING,
510✔
774
                            'type'    => 'T_STRING',
510✔
775
                            'content' => $token[1],
510✔
776
                        ];
340✔
777

778
                        $newStackPtr++;
510✔
779
                        continue 2;
510✔
780
                    }
781

782
                    break;
2,052✔
783
                }
784
            }//end if
785

786
            /*
787
                Prior to PHP 7.4, PHP didn't support stand-alone PHP open tags at the end of a file
788
                (without a new line), so we need to make sure that the tokenization in PHPCS is consistent
789
                cross-version PHP by retokenizing to T_OPEN_TAG.
790
            */
791

792
            if (PHP_VERSION_ID < 70400
3,072✔
793
                && $tokenIsArray === true
3,072✔
794
                // PHP < 7.4 with short open tags off.
795
                && (($stackPtr === ($numTokens - 1)
3,072✔
796
                && $token[0] === T_INLINE_HTML
3,072✔
797
                && stripos($token[1], '<?php') === 0)
2,071✔
798
                // PHP < 7.4 with short open tags on.
2,050✔
799
                || ($stackPtr === ($numTokens - 2)
3,072✔
800
                && $token[0] === T_OPEN_TAG
3,072✔
801
                && $token[1] === '<?'
3,072✔
802
                && is_array($tokens[($stackPtr + 1)]) === true
3,072✔
803
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,072✔
804
                && strtolower($tokens[($stackPtr + 1)][1]) === 'php'))
3,072✔
805
            ) {
806
                if ($token[0] === T_INLINE_HTML) {
2✔
807
                    $finalTokens[$newStackPtr] = [
2✔
808
                        'code'    => T_OPEN_TAG,
2✔
809
                        'type'    => 'T_OPEN_TAG',
2✔
810
                        'content' => $token[1],
2✔
811
                    ];
812
                } else {
813
                    $finalTokens[$newStackPtr] = [
×
814
                        'code'    => T_OPEN_TAG,
×
815
                        'type'    => 'T_OPEN_TAG',
×
816
                        'content' => $token[1].$tokens[($stackPtr + 1)][1],
×
817
                    ];
818

819
                    $stackPtr++;
×
820
                }
821

822
                $newStackPtr++;
2✔
823
                continue;
2✔
824
            }//end if
825

826
            /*
827
                Split whitespace off from long PHP open tag tokens and potentially join the whitespace
828
                with a subsequent whitespace token.
829
            */
830

831
            if ($tokenIsArray === true
3,072✔
832
                && $token[0] === T_OPEN_TAG
3,072✔
833
                && stripos($token[1], '<?php') === 0
3,072✔
834
            ) {
835
                $openTagAndWhiteSpace = str_split($token[1], 5);
3,072✔
836

837
                $finalTokens[$newStackPtr] = [
3,072✔
838
                    'code'    => T_OPEN_TAG,
3,072✔
839
                    'type'    => 'T_OPEN_TAG',
3,072✔
840
                    'content' => $openTagAndWhiteSpace[0],
3,072✔
841
                ];
2,050✔
842
                $newStackPtr++;
3,072✔
843

844
                if (isset($openTagAndWhiteSpace[1]) === true) {
3,072✔
845
                    // The original open tag token included whitespace.
846
                    // Check whether a new whitespace token needs to be inserted or if the
847
                    // whitespace needs to be joined with a pre-existing whitespace
848
                    // token on the same line as the open tag.
849
                    if (isset($tokens[($stackPtr + 1)]) === true
3,072✔
850
                        && $openTagAndWhiteSpace[1] === ' '
3,072✔
851
                        && is_array($tokens[($stackPtr + 1)]) === true
3,072✔
852
                        && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,072✔
853
                    ) {
854
                        // Adjusting the original token stack as the "new line may be split over two tokens"
855
                        // check should still be run on this token.
856
                        $tokens[($stackPtr + 1)][1] = $openTagAndWhiteSpace[1].$tokens[($stackPtr + 1)][1];
63✔
857

858
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
63✔
859
                            StatusWriter::write("* removed whitespace from T_OPEN_TAG token $stackPtr and merged it with the next token T_WHITESPACE", 2);
44✔
860
                        }
861
                    } else {
862
                        $finalTokens[$newStackPtr] = [
3,072✔
863
                            'code'    => T_WHITESPACE,
3,072✔
864
                            'type'    => 'T_WHITESPACE',
3,072✔
865
                            'content' => $openTagAndWhiteSpace[1],
3,072✔
866
                        ];
2,050✔
867

868
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,072✔
NEW
869
                            StatusWriter::write("* T_OPEN_TAG token $stackPtr split into T_OPEN_TAG (without whitespace) and new T_WHITESPACE token", 2);
×
870
                        }
871

872
                        $newStackPtr++;
3,072✔
873
                    }//end if
874
                }//end if
875

876
                continue;
3,072✔
877
            }//end if
878

879
            /*
880
                Parse doc blocks into something that can be easily iterated over.
881
            */
882

883
            if ($tokenIsArray === true
3,072✔
884
                && ($token[0] === T_DOC_COMMENT
3,072✔
885
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
3,072✔
886
            ) {
887
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
93✔
888
                foreach ($commentTokens as $commentToken) {
93✔
889
                    $finalTokens[$newStackPtr] = $commentToken;
93✔
890
                    $newStackPtr++;
93✔
891
                }
892

893
                continue;
93✔
894
            }
895

896
            /*
897
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
898
            */
899

900
            if (PHP_VERSION_ID >= 80000
3,072✔
901
                && $tokenIsArray === true
3,072✔
902
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
3,072✔
903
                && isset($tokens[($stackPtr + 1)]) === true
3,072✔
904
                && is_array($tokens[($stackPtr + 1)]) === true
3,072✔
905
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,072✔
906
            ) {
907
                $nextToken = $tokens[($stackPtr + 1)];
1,548✔
908

909
                // If the next token is a single new line, merge it into the comment token
910
                // and set to it up to be skipped.
911
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
1,548✔
912
                    $token[1] .= $nextToken[1];
1,112✔
913
                    $tokens[($stackPtr + 1)] = null;
1,112✔
914

915
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,112✔
916
                        StatusWriter::write("* merged newline after comment into comment token $stackPtr", 2);
556✔
917
                    }
918
                } else {
919
                    // This may be a whitespace token consisting of multiple new lines.
920
                    if (strpos($nextToken[1], "\r\n") === 0) {
1,208✔
921
                        $token[1] .= "\r\n";
30✔
922
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
30✔
923
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
1,178✔
924
                        $token[1] .= "\n\r";
×
925
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
926
                    } else if (strpos($nextToken[1], "\n") === 0) {
1,178✔
927
                        $token[1] .= "\n";
1,178✔
928
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
1,178✔
929
                    }
930

931
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,208✔
932
                        StatusWriter::write("* stripped first newline after comment and added it to comment token $stackPtr", 2);
×
933
                    }
934
                }//end if
935
            }//end if
936

937
            /*
938
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
939
                T_LNUMBER and T_STRING token values into a single token value, and
940
                then ignore the T_STRING token.
941
            */
942

943
            if (PHP_VERSION_ID < 80100
3,072✔
944
                && $tokenIsArray === true && $token[1] === '0'
3,072✔
945
                && (isset($tokens[($stackPtr + 1)]) === true
2,609✔
946
                && is_array($tokens[($stackPtr + 1)]) === true
2,609✔
947
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,609✔
948
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,609✔
949
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,609✔
950
                && $tokens[($stackPtr + 1)][1][1] !== '_')
3,072✔
951
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
3,072✔
952
            ) {
953
                $finalTokens[$newStackPtr] = [
37✔
954
                    'code'    => T_LNUMBER,
37✔
955
                    'type'    => 'T_LNUMBER',
37✔
956
                    'content' => $token[1] .= $matches[1],
37✔
957
                ];
958
                $newStackPtr++;
37✔
959

960
                if (isset($matches[2]) === true && $matches[2] !== '') {
37✔
961
                    $type = 'T_LNUMBER';
10✔
962
                    if ($matches[2][0] === '_') {
10✔
963
                        $type = 'T_STRING';
10✔
964
                    }
965

966
                    $finalTokens[$newStackPtr] = [
10✔
967
                        'code'    => constant($type),
10✔
968
                        'type'    => $type,
10✔
969
                        'content' => $matches[2],
10✔
970
                    ];
971
                    $newStackPtr++;
10✔
972
                }
973

974
                $stackPtr++;
37✔
975
                continue;
37✔
976
            }//end if
977

978
            /*
979
                PHP 8.1 introduced two dedicated tokens for the & character.
980
                Retokenizing both of these to T_BITWISE_AND, which is the
981
                token PHPCS already tokenized them as.
982
            */
983

984
            if ($tokenIsArray === true
3,072✔
985
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
3,072✔
986
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
3,072✔
987
            ) {
988
                $finalTokens[$newStackPtr] = [
860✔
989
                    'code'    => T_BITWISE_AND,
860✔
990
                    'type'    => 'T_BITWISE_AND',
860✔
991
                    'content' => $token[1],
860✔
992
                ];
860✔
993
                $newStackPtr++;
860✔
994
                continue;
860✔
995
            }
996

997
            /*
998
                If this is a double quoted string, PHP will tokenize the whole
999
                thing which causes problems with the scope map when braces are
1000
                within the string. So we need to merge the tokens together to
1001
                provide a single string.
1002
            */
1003

1004
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
3,072✔
1005
                // Binary casts need a special token.
1006
                if ($token[0] === 'b"') {
105✔
1007
                    $finalTokens[$newStackPtr] = [
×
1008
                        'code'    => T_BINARY_CAST,
×
1009
                        'type'    => 'T_BINARY_CAST',
×
1010
                        'content' => 'b',
×
1011
                    ];
1012
                    $newStackPtr++;
×
1013
                }
1014

1015
                $tokenContent = '"';
105✔
1016
                $nestedVars   = [];
105✔
1017
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
105✔
1018
                    $subToken        = (array) $tokens[$i];
105✔
1019
                    $subTokenIsArray = isset($subToken[1]);
105✔
1020

1021
                    if ($subTokenIsArray === true) {
105✔
1022
                        $tokenContent .= $subToken[1];
105✔
1023
                        if (($subToken[1] === '{'
105✔
1024
                            || $subToken[1] === '${')
105✔
1025
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
105✔
1026
                        ) {
1027
                            $nestedVars[] = $i;
90✔
1028
                        }
1029
                    } else {
1030
                        $tokenContent .= $subToken[0];
105✔
1031
                        if ($subToken[0] === '}') {
105✔
1032
                            array_pop($nestedVars);
60✔
1033
                        }
1034
                    }
1035

1036
                    if ($subTokenIsArray === false
105✔
1037
                        && $subToken[0] === '"'
105✔
1038
                        && empty($nestedVars) === true
105✔
1039
                    ) {
1040
                        // We found the other end of the double quoted string.
1041
                        break;
105✔
1042
                    }
1043
                }//end for
1044

1045
                $stackPtr = $i;
105✔
1046

1047
                // Convert each line within the double quoted string to a
1048
                // new token, so it conforms with other multiple line tokens.
1049
                $tokenLines = explode($this->eolChar, $tokenContent);
105✔
1050
                $numLines   = count($tokenLines);
105✔
1051
                $newToken   = [];
105✔
1052

1053
                for ($j = 0; $j < $numLines; $j++) {
105✔
1054
                    $newToken['content'] = $tokenLines[$j];
105✔
1055
                    if ($j === ($numLines - 1)) {
105✔
1056
                        if ($tokenLines[$j] === '') {
105✔
1057
                            break;
90✔
1058
                        }
1059
                    } else {
1060
                        $newToken['content'] .= $this->eolChar;
60✔
1061
                    }
1062

1063
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
105✔
1064
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
105✔
1065
                    $finalTokens[$newStackPtr] = $newToken;
105✔
1066
                    $newStackPtr++;
105✔
1067
                }
1068

1069
                // Continue, as we're done with this token.
1070
                continue;
105✔
1071
            }//end if
1072

1073
            /*
1074
                Detect binary casting and assign the casts their own token.
1075
            */
1076

1077
            if ($tokenIsArray === true
3,072✔
1078
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
3,072✔
1079
                && (substr($token[1], 0, 2) === 'b"'
2,964✔
1080
                || substr($token[1], 0, 2) === "b'")
3,072✔
1081
            ) {
1082
                $finalTokens[$newStackPtr] = [
×
1083
                    'code'    => T_BINARY_CAST,
×
1084
                    'type'    => 'T_BINARY_CAST',
×
1085
                    'content' => 'b',
×
1086
                ];
1087
                $newStackPtr++;
×
1088
                $token[1] = substr($token[1], 1);
×
1089
            }
1090

1091
            if ($tokenIsArray === true
3,072✔
1092
                && $token[0] === T_STRING_CAST
3,072✔
1093
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
3,072✔
1094
            ) {
1095
                $finalTokens[$newStackPtr] = [
×
1096
                    'code'    => T_BINARY_CAST,
×
1097
                    'type'    => 'T_BINARY_CAST',
×
1098
                    'content' => $token[1],
×
1099
                ];
1100
                $newStackPtr++;
×
1101
                continue;
×
1102
            }
1103

1104
            /*
1105
                If this is a heredoc, PHP will tokenize the whole
1106
                thing which causes problems when heredocs don't
1107
                contain real PHP code, which is almost never.
1108
                We want to leave the start and end heredoc tokens
1109
                alone though.
1110
            */
1111

1112
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
3,072✔
1113
                // Add the start heredoc token to the final array.
1114
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
135✔
1115

1116
                // Check if this is actually a nowdoc and use a different token
1117
                // to help the sniffs.
1118
                $nowdoc = false;
135✔
1119
                if (strpos($token[1], "'") !== false) {
135✔
1120
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
18✔
1121
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
18✔
1122
                    $nowdoc = true;
18✔
1123
                }
1124

1125
                $tokenContent = '';
135✔
1126
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
135✔
1127
                    $subTokenIsArray = is_array($tokens[$i]);
135✔
1128
                    if ($subTokenIsArray === true
135✔
1129
                        && $tokens[$i][0] === T_END_HEREDOC
135✔
1130
                    ) {
1131
                        // We found the other end of the heredoc.
1132
                        break;
132✔
1133
                    }
1134

1135
                    if ($subTokenIsArray === true) {
135✔
1136
                        $tokenContent .= $tokens[$i][1];
135✔
1137
                    } else {
1138
                        $tokenContent .= $tokens[$i];
114✔
1139
                    }
1140
                }
1141

1142
                if ($i === $numTokens) {
135✔
1143
                    // We got to the end of the file and never
1144
                    // found the closing token, so this probably wasn't
1145
                    // a heredoc.
1146
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
1147
                        $type = $finalTokens[$newStackPtr]['type'];
×
1148
                        StatusWriter::write('* failed to find the end of the here/nowdoc', 2);
×
1149
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1150
                    }
1151

1152
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
3✔
1153
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
3✔
1154
                    $newStackPtr++;
3✔
1155
                    continue;
3✔
1156
                }
1157

1158
                $stackPtr = $i;
132✔
1159
                $newStackPtr++;
132✔
1160

1161
                // Convert each line within the heredoc to a
1162
                // new token, so it conforms with other multiple line tokens.
1163
                $tokenLines = explode($this->eolChar, $tokenContent);
132✔
1164
                $numLines   = count($tokenLines);
132✔
1165
                $newToken   = [];
132✔
1166

1167
                for ($j = 0; $j < $numLines; $j++) {
132✔
1168
                    $newToken['content'] = $tokenLines[$j];
132✔
1169
                    if ($j === ($numLines - 1)) {
132✔
1170
                        if ($tokenLines[$j] === '') {
132✔
1171
                            break;
132✔
1172
                        }
1173
                    } else {
1174
                        $newToken['content'] .= $this->eolChar;
132✔
1175
                    }
1176

1177
                    if ($nowdoc === true) {
132✔
1178
                        $newToken['code'] = T_NOWDOC;
18✔
1179
                        $newToken['type'] = 'T_NOWDOC';
18✔
1180
                    } else {
1181
                        $newToken['code'] = T_HEREDOC;
132✔
1182
                        $newToken['type'] = 'T_HEREDOC';
132✔
1183
                    }
1184

1185
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1186
                    $newStackPtr++;
132✔
1187
                }//end for
1188

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

1192
                if ($nowdoc === true) {
132✔
1193
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
18✔
1194
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
18✔
1195
                }
1196

1197
                $newStackPtr++;
132✔
1198

1199
                // Continue, as we're done with this token.
1200
                continue;
132✔
1201
            }//end if
1202

1203
            /*
1204
                Enum keyword for PHP < 8.1
1205
            */
1206

1207
            if ($tokenIsArray === true
3,072✔
1208
                && $token[0] === T_STRING
3,072✔
1209
                && strtolower($token[1]) === 'enum'
3,072✔
1210
            ) {
1211
                // Get the next non-empty token.
1212
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,356✔
1213
                    if (is_array($tokens[$i]) === false
1,356✔
1214
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,356✔
1215
                    ) {
1216
                        break;
1,356✔
1217
                    }
1218
                }
1219

1220
                if (isset($tokens[$i]) === true
1,356✔
1221
                    && is_array($tokens[$i]) === true
1,356✔
1222
                    && $tokens[$i][0] === T_STRING
1,356✔
1223
                ) {
1224
                    // Modify $tokens directly so we can use it later when converting enum "case".
1225
                    $tokens[$stackPtr][0] = T_ENUM;
313✔
1226

1227
                    $newToken            = [];
313✔
1228
                    $newToken['code']    = T_ENUM;
313✔
1229
                    $newToken['type']    = 'T_ENUM';
313✔
1230
                    $newToken['content'] = $token[1];
313✔
1231
                    $finalTokens[$newStackPtr] = $newToken;
313✔
1232

1233
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
313✔
1234
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_ENUM", 2);
×
1235
                    }
1236

1237
                    $newStackPtr++;
313✔
1238
                    continue;
313✔
1239
                }
1240
            }//end if
1241

1242
            /*
1243
                Convert enum "case" to T_ENUM_CASE
1244
            */
1245

1246
            if ($tokenIsArray === true
3,072✔
1247
                && $token[0] === T_CASE
3,072✔
1248
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,072✔
1249
            ) {
1250
                $isEnumCase = false;
1,554✔
1251
                $scope      = 1;
1,554✔
1252

1253
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,554✔
1254
                    if ($tokens[$i] === '}') {
1,554✔
1255
                        $scope++;
1,509✔
1256
                        continue;
1,509✔
1257
                    }
1258

1259
                    if ($tokens[$i] === '{') {
1,554✔
1260
                        $scope--;
1,554✔
1261
                        continue;
1,554✔
1262
                    }
1263

1264
                    if (is_array($tokens[$i]) === false) {
1,554✔
1265
                        continue;
1,554✔
1266
                    }
1267

1268
                    if ($scope !== 0) {
1,554✔
1269
                        continue;
1,554✔
1270
                    }
1271

1272
                    if ($tokens[$i][0] === T_SWITCH) {
1,044✔
1273
                        break;
978✔
1274
                    }
1275

1276
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,044✔
1277
                        $isEnumCase = true;
129✔
1278
                        break;
129✔
1279
                    }
1280
                }//end for
1281

1282
                if ($isEnumCase === true) {
1,554✔
1283
                    // Modify $tokens directly so we can use it as optimisation for other enum "case".
1284
                    $tokens[$stackPtr][0] = T_ENUM_CASE;
129✔
1285

1286
                    $newToken            = [];
129✔
1287
                    $newToken['code']    = T_ENUM_CASE;
129✔
1288
                    $newToken['type']    = 'T_ENUM_CASE';
129✔
1289
                    $newToken['content'] = $token[1];
129✔
1290
                    $finalTokens[$newStackPtr] = $newToken;
129✔
1291

1292
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
129✔
1293
                        StatusWriter::write("* token $stackPtr changed from T_CASE to T_ENUM_CASE", 2);
×
1294
                    }
1295

1296
                    $newStackPtr++;
129✔
1297
                    continue;
129✔
1298
                }
1299
            }//end if
1300

1301
            /*
1302
                As of PHP 8.0 fully qualified, partially qualified and namespace relative
1303
                identifier names are tokenized differently.
1304
                This "undoes" the new tokenization so the tokenization will be the same in
1305
                in PHP 5, 7 and 8.
1306
            */
1307

1308
            if (PHP_VERSION_ID >= 80000
3,072✔
1309
                && $tokenIsArray === true
3,072✔
1310
                && ($token[0] === T_NAME_QUALIFIED
2,050✔
1311
                || $token[0] === T_NAME_FULLY_QUALIFIED
2,050✔
1312
                || $token[0] === T_NAME_RELATIVE)
3,072✔
1313
            ) {
1314
                $name = $token[1];
1,244✔
1315

1316
                if ($token[0] === T_NAME_FULLY_QUALIFIED) {
1,244✔
1317
                    $newToken            = [];
708✔
1318
                    $newToken['code']    = T_NS_SEPARATOR;
708✔
1319
                    $newToken['type']    = 'T_NS_SEPARATOR';
708✔
1320
                    $newToken['content'] = '\\';
708✔
1321
                    $finalTokens[$newStackPtr] = $newToken;
708✔
1322
                    ++$newStackPtr;
708✔
1323

1324
                    $name = ltrim($name, '\\');
708✔
1325
                }
1326

1327
                if ($token[0] === T_NAME_RELATIVE) {
1,244✔
1328
                    $newToken            = [];
1,180✔
1329
                    $newToken['code']    = T_NAMESPACE;
1,180✔
1330
                    $newToken['type']    = 'T_NAMESPACE';
1,180✔
1331
                    $newToken['content'] = substr($name, 0, 9);
1,180✔
1332
                    $finalTokens[$newStackPtr] = $newToken;
1,180✔
1333
                    ++$newStackPtr;
1,180✔
1334

1335
                    $newToken            = [];
1,180✔
1336
                    $newToken['code']    = T_NS_SEPARATOR;
1,180✔
1337
                    $newToken['type']    = 'T_NS_SEPARATOR';
1,180✔
1338
                    $newToken['content'] = '\\';
1,180✔
1339
                    $finalTokens[$newStackPtr] = $newToken;
1,180✔
1340
                    ++$newStackPtr;
1,180✔
1341

1342
                    $name = substr($name, 10);
1,180✔
1343
                }
1344

1345
                $parts     = explode('\\', $name);
1,244✔
1346
                $partCount = count($parts);
1,244✔
1347
                $lastPart  = ($partCount - 1);
1,244✔
1348

1349
                foreach ($parts as $i => $part) {
1,244✔
1350
                    $newToken            = [];
1,244✔
1351
                    $newToken['code']    = T_STRING;
1,244✔
1352
                    $newToken['type']    = 'T_STRING';
1,244✔
1353
                    $newToken['content'] = $part;
1,244✔
1354
                    $finalTokens[$newStackPtr] = $newToken;
1,244✔
1355
                    ++$newStackPtr;
1,244✔
1356

1357
                    if ($i !== $lastPart) {
1,244✔
1358
                        $newToken            = [];
904✔
1359
                        $newToken['code']    = T_NS_SEPARATOR;
904✔
1360
                        $newToken['type']    = 'T_NS_SEPARATOR';
904✔
1361
                        $newToken['content'] = '\\';
904✔
1362
                        $finalTokens[$newStackPtr] = $newToken;
904✔
1363
                        ++$newStackPtr;
904✔
1364
                    }
1365
                }
1366

1367
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,244✔
1368
                    $type    = Tokens::tokenName($token[0]);
×
1369
                    $content = Common::prepareForOutput($token[1]);
×
1370
                    StatusWriter::write("* token $stackPtr split into individual tokens; was: $type => $content", 2);
×
1371
                }
1372

1373
                continue;
1,244✔
1374
            }//end if
1375

1376
            /*
1377
                PHP 8.0 Attributes
1378
            */
1379

1380
            if (PHP_VERSION_ID < 80000
3,072✔
1381
                && $token[0] === T_COMMENT
3,072✔
1382
                && strpos($token[1], '#[') === 0
3,072✔
1383
            ) {
1384
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
17✔
1385
                if ($subTokens !== null) {
17✔
1386
                    array_splice($tokens, $stackPtr, 1, $subTokens);
17✔
1387
                    $numTokens = count($tokens);
17✔
1388

1389
                    $tokenIsArray = true;
17✔
1390
                    $token        = $tokens[$stackPtr];
17✔
1391
                } else {
1392
                    $token[0] = T_ATTRIBUTE;
17✔
1393
                }
1394
            }
1395

1396
            if ($tokenIsArray === true
3,072✔
1397
                && $token[0] === T_ATTRIBUTE
3,072✔
1398
            ) {
1399
                // Go looking for the close bracket.
1400
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1401

1402
                $newToken            = [];
51✔
1403
                $newToken['code']    = T_ATTRIBUTE;
51✔
1404
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1405
                $newToken['content'] = '#[';
51✔
1406
                $finalTokens[$newStackPtr] = $newToken;
51✔
1407

1408
                $tokens[$bracketCloser]    = [];
51✔
1409
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1410
                $tokens[$bracketCloser][1] = ']';
51✔
1411

1412
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1413
                    StatusWriter::write("* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END", 2);
×
1414
                }
1415

1416
                $newStackPtr++;
51✔
1417
                continue;
51✔
1418
            }//end if
1419

1420
            /*
1421
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1422
                token and ensures that the colon after it is always T_COLON.
1423
            */
1424

1425
            if ($tokenIsArray === true
3,072✔
1426
                && ($token[0] === T_STRING
3,072✔
1427
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
3,072✔
1428
            ) {
1429
                // Get the next non-empty token.
1430
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,886✔
1431
                    if (is_array($tokens[$i]) === false
2,886✔
1432
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
2,886✔
1433
                    ) {
1434
                        break;
2,886✔
1435
                    }
1436
                }
1437

1438
                if (isset($tokens[$i]) === true
2,886✔
1439
                    && is_array($tokens[$i]) === false
2,886✔
1440
                    && $tokens[$i] === ':'
2,886✔
1441
                ) {
1442
                    // Get the previous non-empty token.
1443
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,626✔
1444
                        if (is_array($tokens[$j]) === false
1,626✔
1445
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
1,626✔
1446
                        ) {
1447
                            break;
1,626✔
1448
                        }
1449
                    }
1450

1451
                    if (is_array($tokens[$j]) === false
1,626✔
1452
                        && ($tokens[$j] === '('
1,576✔
1453
                        || $tokens[$j] === ',')
1,626✔
1454
                    ) {
1455
                        $newToken            = [];
690✔
1456
                        $newToken['code']    = T_PARAM_NAME;
690✔
1457
                        $newToken['type']    = 'T_PARAM_NAME';
690✔
1458
                        $newToken['content'] = $token[1];
690✔
1459
                        $finalTokens[$newStackPtr] = $newToken;
690✔
1460

1461
                        $newStackPtr++;
690✔
1462

1463
                        // Modify the original token stack so that future checks, like
1464
                        // determining T_COLON vs T_INLINE_ELSE can handle this correctly.
1465
                        $tokens[$stackPtr][0] = T_PARAM_NAME;
690✔
1466

1467
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
690✔
1468
                            $type = Tokens::tokenName($token[0]);
×
1469
                            StatusWriter::write("* token $stackPtr changed from $type to T_PARAM_NAME", 2);
×
1470
                        }
1471

1472
                        continue;
690✔
1473
                    }
1474
                }//end if
1475
            }//end if
1476

1477
            /*
1478
                "readonly" keyword for PHP < 8.1
1479
            */
1480

1481
            if ($tokenIsArray === true
3,072✔
1482
                && strtolower($token[1]) === 'readonly'
3,072✔
1483
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,382✔
1484
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
3,072✔
1485
            ) {
1486
                // Get the next non-whitespace token.
1487
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
996✔
1488
                    if (is_array($tokens[$i]) === false
996✔
1489
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
996✔
1490
                    ) {
1491
                        break;
996✔
1492
                    }
1493
                }
1494

1495
                $isReadonlyKeyword = false;
996✔
1496

1497
                if (isset($tokens[$i]) === false
996✔
1498
                    || $tokens[$i] !== '('
996✔
1499
                ) {
1500
                    $isReadonlyKeyword = true;
996✔
1501
                } else if ($tokens[$i] === '(') {
×
1502
                    /*
1503
                     * Skip over tokens which can be used in type declarations.
1504
                     * At this point, the only token types which need to be taken into consideration
1505
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1506
                     * and the union/intersection/dnf parentheses.
1507
                     */
1508

1509
                    $foundDNFParens = 1;
×
1510
                    $foundDNFPipe   = 0;
×
1511

1512
                    for (++$i; $i < $numTokens; $i++) {
×
1513
                        if (is_array($tokens[$i]) === true) {
×
1514
                            $tokenType = $tokens[$i][0];
×
1515
                        } else {
1516
                            $tokenType = $tokens[$i];
×
1517
                        }
1518

1519
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1520
                            continue;
×
1521
                        }
1522

1523
                        if ($tokenType === '|') {
×
1524
                            ++$foundDNFPipe;
×
1525
                            continue;
×
1526
                        }
1527

1528
                        if ($tokenType === ')') {
×
1529
                            ++$foundDNFParens;
×
1530
                            continue;
×
1531
                        }
1532

1533
                        if ($tokenType === '(') {
×
1534
                            ++$foundDNFParens;
×
1535
                            continue;
×
1536
                        }
1537

1538
                        if ($tokenType === T_STRING
×
1539
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1540
                            || $tokenType === T_NAME_RELATIVE
×
1541
                            || $tokenType === T_NAME_QUALIFIED
×
1542
                            || $tokenType === T_ARRAY
×
1543
                            || $tokenType === T_NAMESPACE
×
1544
                            || $tokenType === T_NS_SEPARATOR
×
1545
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1546
                            || $tokenType === '&' // PHP < 8.0.
×
1547
                        ) {
1548
                            continue;
×
1549
                        }
1550

1551
                        // Reached the next token after.
1552
                        if (($foundDNFParens % 2) === 0
×
1553
                            && $foundDNFPipe >= 1
×
1554
                            && ($tokenType === T_VARIABLE
×
1555
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1556
                        ) {
1557
                            $isReadonlyKeyword = true;
×
1558
                        }
1559

1560
                        break;
×
1561
                    }//end for
1562
                }//end if
1563

1564
                if ($isReadonlyKeyword === true) {
996✔
1565
                    $finalTokens[$newStackPtr] = [
996✔
1566
                        'code'    => T_READONLY,
996✔
1567
                        'type'    => 'T_READONLY',
996✔
1568
                        'content' => $token[1],
996✔
1569
                    ];
664✔
1570
                    $newStackPtr++;
996✔
1571

1572
                    // Also modify the original token stack so that
1573
                    // future checks (like looking for T_NULLABLE) can
1574
                    // detect the T_READONLY token more easily.
1575
                    $tokens[$stackPtr][0] = T_READONLY;
996✔
1576
                    $token[0] = T_READONLY;
996✔
1577

1578
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
996✔
1579
                        StatusWriter::write("* token $stackPtr changed from $type to T_READONLY", 2);
664✔
1580
                    }
1581
                } else {
1582
                    $finalTokens[$newStackPtr] = [
×
1583
                        'code'    => T_STRING,
×
1584
                        'type'    => 'T_STRING',
×
1585
                        'content' => $token[1],
×
1586
                    ];
1587
                    $newStackPtr++;
×
1588

1589
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1590
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1591
                    }
1592
                }//end if
1593

1594
                continue;
996✔
1595
            }//end if
1596

1597
            /*
1598
                Deal with "yield from" in various PHP versions.
1599
            */
1600

1601
            if (PHP_VERSION_ID < 80300
3,072✔
1602
                && $tokenIsArray === true
3,072✔
1603
                && $token[0] === T_STRING
3,072✔
1604
                && strtolower($token[1]) === 'from'
3,072✔
1605
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
3,072✔
1606
            ) {
1607
                /*
1608
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1609
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1610
                    We want to keep the tokenization of the tokens between, but need to change the
1611
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1612
                */
1613

1614
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
36✔
1615
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
36✔
1616

1617
                $finalTokens[$newStackPtr] = [
36✔
1618
                    'code'    => T_YIELD_FROM,
36✔
1619
                    'type'    => 'T_YIELD_FROM',
36✔
1620
                    'content' => $token[1],
36✔
1621
                ];
18✔
1622
                $newStackPtr++;
36✔
1623

1624
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
36✔
1625
                    StatusWriter::write("* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD", 2);
×
1626
                    StatusWriter::write("* token $stackPtr changed into T_YIELD_FROM; was: T_STRING", 2);
×
1627
                }
1628

1629
                continue;
36✔
1630
            } else if ($tokenIsArray === true
3,072✔
1631
                && $token[0] === T_YIELD_FROM
3,072✔
1632
                && strpos($token[1], $this->eolChar) !== false
3,072✔
1633
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
3,072✔
1634
            ) {
1635
                /*
1636
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1637
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1638
                    separately for consistency.
1639
                */
1640

1641
                $finalTokens[$newStackPtr] = [
54✔
1642
                    'code'    => T_YIELD_FROM,
54✔
1643
                    'type'    => 'T_YIELD_FROM',
54✔
1644
                    'content' => substr($token[1], 0, 5),
54✔
1645
                ];
36✔
1646
                $newStackPtr++;
54✔
1647

1648
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
54✔
1649
                $numLines   = count($tokenLines);
54✔
1650
                $newToken   = [
36✔
1651
                    'type'    => 'T_WHITESPACE',
54✔
1652
                    'code'    => T_WHITESPACE,
54✔
1653
                    'content' => '',
54✔
1654
                ];
36✔
1655

1656
                foreach ($tokenLines as $i => $line) {
54✔
1657
                    $newToken['content'] = $line;
54✔
1658
                    if ($i === ($numLines - 1)) {
54✔
1659
                        if ($line === '') {
54✔
1660
                            break;
36✔
1661
                        }
1662
                    } else {
1663
                        $newToken['content'] .= $this->eolChar;
54✔
1664
                    }
1665

1666
                    $finalTokens[$newStackPtr] = $newToken;
54✔
1667
                    $newStackPtr++;
54✔
1668
                }
1669

1670
                $finalTokens[$newStackPtr] = [
54✔
1671
                    'code'    => T_YIELD_FROM,
54✔
1672
                    'type'    => 'T_YIELD_FROM',
54✔
1673
                    'content' => substr($token[1], -4),
54✔
1674
                ];
36✔
1675
                $newStackPtr++;
54✔
1676

1677
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
1678
                    StatusWriter::write("* token $stackPtr split into 'yield', one or more whitespace tokens and 'from'", 2);
×
1679
                }
1680

1681
                continue;
54✔
1682
            } else if (PHP_VERSION_ID >= 80300
3,072✔
1683
                && $tokenIsArray === true
3,072✔
1684
                && $token[0] === T_YIELD_FROM
3,072✔
1685
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
3,072✔
1686
                && stripos($token[1], 'yield') === 0
3,072✔
1687
            ) {
1688
                /*
1689
                    Since PHP 8.3, "yield from" allows for comments and will
1690
                    swallow the comment in the `T_YIELD_FROM` token.
1691
                    We need to split this up to allow for sniffs handling comments.
1692
                */
1693

1694
                $finalTokens[$newStackPtr] = [
18✔
1695
                    'code'    => T_YIELD_FROM,
18✔
1696
                    'type'    => 'T_YIELD_FROM',
18✔
1697
                    'content' => substr($token[1], 0, 5),
18✔
1698
                ];
18✔
1699
                $newStackPtr++;
18✔
1700

1701
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
18✔
1702
                // Remove the PHP open tag token.
1703
                array_shift($yieldFromSubtokens);
18✔
1704
                // Add the "from" keyword.
1705
                $yieldFromSubtokens[] = [
18✔
1706
                    0 => T_YIELD_FROM,
18✔
1707
                    1 => substr($token[1], -4),
18✔
1708
                ];
18✔
1709

1710
                // Inject the new tokens into the token stack.
1711
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
18✔
1712
                $numTokens = count($tokens);
18✔
1713

1714
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
1715
                    StatusWriter::write("* token $stackPtr split into parts (yield from with comment)", 2);
×
1716
                }
1717

1718
                unset($yieldFromSubtokens);
18✔
1719
                continue;
18✔
1720
            }//end if
1721

1722
            /*
1723
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1724
                T_COALESCE, T_EQUAL.
1725
                So look for and combine these tokens in earlier versions.
1726
            */
1727

1728
            if ($tokenIsArray === true
3,072✔
1729
                && $token[0] === T_COALESCE
3,072✔
1730
                && isset($tokens[($stackPtr + 1)]) === true
3,072✔
1731
                && $tokens[($stackPtr + 1)][0] === '='
3,072✔
1732
            ) {
1733
                $newToken            = [];
×
1734
                $newToken['code']    = T_COALESCE_EQUAL;
×
1735
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1736
                $newToken['content'] = '??=';
×
1737
                $finalTokens[$newStackPtr] = $newToken;
×
1738

1739
                $newStackPtr++;
×
1740
                $stackPtr++;
×
1741

1742
                continue;
×
1743
            }
1744

1745
            /*
1746
                Before PHP 8, the ?-> operator was tokenized as
1747
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1748
                So look for and combine these tokens in earlier versions.
1749
            */
1750

1751
            if ($tokenIsArray === false
3,072✔
1752
                && $token[0] === '?'
3,072✔
1753
                && isset($tokens[($stackPtr + 1)]) === true
3,072✔
1754
                && is_array($tokens[($stackPtr + 1)]) === true
3,072✔
1755
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
3,072✔
1756
            ) {
1757
                $newToken            = [];
25✔
1758
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
25✔
1759
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
25✔
1760
                $newToken['content'] = '?->';
25✔
1761
                $finalTokens[$newStackPtr] = $newToken;
25✔
1762

1763
                $newStackPtr++;
25✔
1764
                $stackPtr++;
25✔
1765
                continue;
25✔
1766
            }
1767

1768
            /*
1769
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1770
                tokens split the token with a T_STRING. So look for
1771
                and change these tokens in earlier versions.
1772
            */
1773

1774
            if (PHP_VERSION_ID < 70400
3,072✔
1775
                && ($tokenIsArray === true
3,072✔
1776
                && ($token[0] === T_LNUMBER
3,072✔
1777
                || $token[0] === T_DNUMBER)
3,072✔
1778
                && isset($tokens[($stackPtr + 1)]) === true
3,072✔
1779
                && is_array($tokens[($stackPtr + 1)]) === true
3,072✔
1780
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,072✔
1781
                && $tokens[($stackPtr + 1)][1][0] === '_')
3,072✔
1782
            ) {
1783
                $newContent = $token[1];
27✔
1784
                $newType    = $token[0];
27✔
1785
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
27✔
1786
                    if (is_array($tokens[$i]) === false) {
27✔
1787
                        break;
27✔
1788
                    }
1789

1790
                    if ($tokens[$i][0] === T_LNUMBER
27✔
1791
                        || $tokens[$i][0] === T_DNUMBER
27✔
1792
                    ) {
1793
                        $newContent .= $tokens[$i][1];
27✔
1794
                        continue;
27✔
1795
                    }
1796

1797
                    if ($tokens[$i][0] === T_STRING
27✔
1798
                        && $tokens[$i][1][0] === '_'
27✔
1799
                        && ((strpos($newContent, '0x') === 0
27✔
1800
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
27✔
1801
                        || (strpos($newContent, '0x') !== 0
27✔
1802
                        && substr($newContent, -1) !== '.'
27✔
1803
                        && substr(strtolower($newContent), -1) !== 'e'
27✔
1804
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
27✔
1805
                    ) {
1806
                        $newContent .= $tokens[$i][1];
27✔
1807

1808
                        // Support floats.
1809
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
27✔
1810
                            && ($tokens[($i + 1)] === '-'
27✔
1811
                            || $tokens[($i + 1)] === '+')
27✔
1812
                        ) {
1813
                            $newContent .= $tokens[($i + 1)];
27✔
1814
                            $i++;
27✔
1815
                        }
1816

1817
                        continue;
27✔
1818
                    }//end if
1819

1820
                    break;
27✔
1821
                }//end for
1822

1823
                if ($newType === T_LNUMBER
27✔
1824
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1825
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1826
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1827
                    || (stripos($newContent, '0x') !== 0
27✔
1828
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
27✔
1829
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
27✔
1830
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1831
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
27✔
1832
                ) {
1833
                    $newType = T_DNUMBER;
27✔
1834
                }
1835

1836
                $newToken            = [];
27✔
1837
                $newToken['code']    = $newType;
27✔
1838
                $newToken['type']    = Tokens::tokenName($newType);
27✔
1839
                $newToken['content'] = $newContent;
27✔
1840
                $finalTokens[$newStackPtr] = $newToken;
27✔
1841

1842
                $newStackPtr++;
27✔
1843
                $stackPtr = ($i - 1);
27✔
1844
                continue;
27✔
1845
            }//end if
1846

1847
            /*
1848
                Backfill the T_MATCH token for PHP versions < 8.0 and
1849
                do initial correction for non-match expression T_MATCH tokens
1850
                to T_STRING for PHP >= 8.0.
1851
                A final check for non-match expression T_MATCH tokens is done
1852
                in PHP::processAdditional().
1853
            */
1854

1855
            if ($tokenIsArray === true
3,072✔
1856
                && (($token[0] === T_STRING
3,072✔
1857
                && strtolower($token[1]) === 'match')
2,940✔
1858
                || $token[0] === T_MATCH)
3,072✔
1859
            ) {
1860
                $isMatch = false;
741✔
1861
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
741✔
1862
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
741✔
1863
                        continue;
741✔
1864
                    }
1865

1866
                    if ($tokens[$x] !== '(') {
741✔
1867
                        // This is not a match expression.
1868
                        break;
353✔
1869
                    }
1870

1871
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
741✔
1872
                        // Also not a match expression.
1873
                        break;
183✔
1874
                    }
1875

1876
                    $isMatch = true;
741✔
1877
                    break;
741✔
1878
                }//end for
1879

1880
                if ($isMatch === true && $token[0] === T_STRING) {
741✔
1881
                    $newToken            = [];
247✔
1882
                    $newToken['code']    = T_MATCH;
247✔
1883
                    $newToken['type']    = 'T_MATCH';
247✔
1884
                    $newToken['content'] = $token[1];
247✔
1885

1886
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
247✔
1887
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_MATCH", 2);
×
1888
                    }
1889

1890
                    $finalTokens[$newStackPtr] = $newToken;
247✔
1891
                    $newStackPtr++;
247✔
1892
                    continue;
247✔
1893
                } else if ($isMatch === false && $token[0] === T_MATCH) {
725✔
1894
                    // PHP 8.0, match keyword, but not a match expression.
1895
                    $newToken            = [];
122✔
1896
                    $newToken['code']    = T_STRING;
122✔
1897
                    $newToken['type']    = 'T_STRING';
122✔
1898
                    $newToken['content'] = $token[1];
122✔
1899

1900
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
122✔
1901
                        StatusWriter::write("* token $stackPtr changed from T_MATCH to T_STRING", 2);
×
1902
                    }
1903

1904
                    $finalTokens[$newStackPtr] = $newToken;
122✔
1905
                    $newStackPtr++;
122✔
1906
                    continue;
122✔
1907
                }//end if
1908
            }//end if
1909

1910
            /*
1911
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
1912
                to prevent scope being set and the scope for switch default statements
1913
                breaking.
1914
            */
1915

1916
            if ($tokenIsArray === true
3,072✔
1917
                && $token[0] === T_DEFAULT
3,072✔
1918
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,072✔
1919
            ) {
1920
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,425✔
1921
                    if ($tokens[$x] === ',') {
1,425✔
1922
                        // Skip over potential trailing comma (supported in PHP).
1923
                        continue;
231✔
1924
                    }
1925

1926
                    if (is_array($tokens[$x]) === false
1,425✔
1927
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
1,425✔
1928
                    ) {
1929
                        // Non-empty, non-comma content.
1930
                        break;
1,425✔
1931
                    }
1932
                }
1933

1934
                if (isset($tokens[$x]) === true
1,425✔
1935
                    && is_array($tokens[$x]) === true
1,425✔
1936
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,425✔
1937
                ) {
1938
                    // Modify the original token stack for the double arrow so that
1939
                    // future checks can disregard the double arrow token more easily.
1940
                    // For match expression "case" statements, this is handled
1941
                    // in PHP::processAdditional().
1942
                    $tokens[$x][0] = T_MATCH_ARROW;
741✔
1943
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
741✔
1944
                        StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
1945
                    }
1946

1947
                    $newToken            = [];
741✔
1948
                    $newToken['code']    = T_MATCH_DEFAULT;
741✔
1949
                    $newToken['type']    = 'T_MATCH_DEFAULT';
741✔
1950
                    $newToken['content'] = $token[1];
741✔
1951

1952
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
741✔
1953
                        StatusWriter::write("* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT", 2);
×
1954
                    }
1955

1956
                    $finalTokens[$newStackPtr] = $newToken;
741✔
1957
                    $newStackPtr++;
741✔
1958
                    continue;
741✔
1959
                }//end if
1960
            }//end if
1961

1962
            /*
1963
                Convert ? to T_NULLABLE OR T_INLINE_THEN
1964
            */
1965

1966
            if ($tokenIsArray === false && $token[0] === '?') {
3,072✔
1967
                $newToken            = [];
1,218✔
1968
                $newToken['content'] = '?';
1,218✔
1969

1970
                // For typed constants, we only need to check the token before the ? to be sure.
1971
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,218✔
1972
                    $newToken['code'] = T_NULLABLE;
189✔
1973
                    $newToken['type'] = 'T_NULLABLE';
189✔
1974

1975
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
189✔
1976
                        StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
1977
                    }
1978

1979
                    $finalTokens[$newStackPtr] = $newToken;
189✔
1980
                    $newStackPtr++;
189✔
1981
                    continue;
189✔
1982
                }
1983

1984
                /*
1985
                 * Check if the next non-empty token is one of the tokens which can be used
1986
                 * in type declarations. If not, it's definitely a ternary.
1987
                 * At this point, the only token types which need to be taken into consideration
1988
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
1989
                 */
1990

1991
                $lastRelevantNonEmpty = null;
1,218✔
1992

1993
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,218✔
1994
                    if (is_array($tokens[$i]) === true) {
1,218✔
1995
                        $tokenType = $tokens[$i][0];
1,218✔
1996
                    } else {
1997
                        $tokenType = $tokens[$i];
886✔
1998
                    }
1999

2000
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
1,218✔
2001
                        continue;
1,218✔
2002
                    }
2003

2004
                    if ($tokenType === T_STRING
1,218✔
2005
                        || $tokenType === T_NAME_FULLY_QUALIFIED
1,218✔
2006
                        || $tokenType === T_NAME_RELATIVE
1,218✔
2007
                        || $tokenType === T_NAME_QUALIFIED
1,218✔
2008
                        || $tokenType === T_ARRAY
1,218✔
2009
                        || $tokenType === T_NAMESPACE
1,218✔
2010
                        || $tokenType === T_NS_SEPARATOR
1,218✔
2011
                    ) {
2012
                        $lastRelevantNonEmpty = $tokenType;
886✔
2013
                        continue;
886✔
2014
                    }
2015

2016
                    if (($tokenType !== T_CALLABLE
1,218✔
2017
                        && isset($lastRelevantNonEmpty) === false)
1,218✔
2018
                        || ($lastRelevantNonEmpty === T_ARRAY
886✔
2019
                        && $tokenType === '(')
886✔
2020
                        || (($lastRelevantNonEmpty === T_STRING
1,148✔
2021
                        || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED
812✔
2022
                        || $lastRelevantNonEmpty === T_NAME_RELATIVE
812✔
2023
                        || $lastRelevantNonEmpty === T_NAME_QUALIFIED)
1,148✔
2024
                        && ($tokenType === T_DOUBLE_COLON
1,148✔
2025
                        || $tokenType === '('
1,148✔
2026
                        || $tokenType === ':'))
1,218✔
2027
                    ) {
2028
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,077✔
2029
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2030
                        }
2031

2032
                        $newToken['code'] = T_INLINE_THEN;
1,077✔
2033
                        $newToken['type'] = 'T_INLINE_THEN';
1,077✔
2034

2035
                        $insideInlineIf[] = $stackPtr;
1,077✔
2036

2037
                        $finalTokens[$newStackPtr] = $newToken;
1,077✔
2038
                        $newStackPtr++;
1,077✔
2039
                        continue 2;
1,077✔
2040
                    }
2041

2042
                    break;
141✔
2043
                }//end for
2044

2045
                /*
2046
                 * This can still be a nullable type or a ternary.
2047
                 * Do additional checking.
2048
                 */
2049

2050
                $prevNonEmpty     = null;
162✔
2051
                $lastSeenNonEmpty = null;
162✔
2052

2053
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
162✔
2054
                    if (is_array($tokens[$i]) === true) {
162✔
2055
                        $tokenType = $tokens[$i][0];
162✔
2056
                    } else {
2057
                        $tokenType = $tokens[$i];
162✔
2058
                    }
2059

2060
                    if ($tokenType === T_STATIC
162✔
2061
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
108✔
2062
                        || $lastSeenNonEmpty === '(')
162✔
2063
                    ) {
2064
                        $lastSeenNonEmpty = $tokenType;
×
2065
                        continue;
×
2066
                    }
2067

2068
                    if ($prevNonEmpty === null
162✔
2069
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
162✔
2070
                    ) {
2071
                        // Found the previous non-empty token.
2072
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
162✔
2073
                            $newToken['code'] = T_NULLABLE;
135✔
2074
                            $newToken['type'] = 'T_NULLABLE';
135✔
2075

2076
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2077
                                StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2078
                            }
2079

2080
                            break;
135✔
2081
                        }
2082

2083
                        $prevNonEmpty = $tokenType;
162✔
2084
                    }
2085

2086
                    if ($tokenType === T_FUNCTION
162✔
2087
                        || $tokenType === T_FN
162✔
2088
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
162✔
2089
                        || $tokenType === T_VAR
162✔
2090
                        || $tokenType === T_READONLY
162✔
2091
                    ) {
2092
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
141✔
2093
                            StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2094
                        }
2095

2096
                        $newToken['code'] = T_NULLABLE;
141✔
2097
                        $newToken['type'] = 'T_NULLABLE';
141✔
2098
                        break;
141✔
2099
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
162✔
2100
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
27✔
2101
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2102
                        }
2103

2104
                        $newToken['code'] = T_INLINE_THEN;
27✔
2105
                        $newToken['type'] = 'T_INLINE_THEN';
27✔
2106

2107
                        $insideInlineIf[] = $stackPtr;
27✔
2108
                        break;
27✔
2109
                    }
2110

2111
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
162✔
2112
                        $lastSeenNonEmpty = $tokenType;
162✔
2113
                    }
2114
                }//end for
2115

2116
                $finalTokens[$newStackPtr] = $newToken;
162✔
2117
                $newStackPtr++;
162✔
2118
                continue;
162✔
2119
            }//end if
2120

2121
            /*
2122
                Tokens after a double colon may look like scope openers,
2123
                such as when writing code like Foo::NAMESPACE, but they are
2124
                only ever variables or strings.
2125
            */
2126

2127
            if ($stackPtr > 1
3,072✔
2128
                && (is_array($tokens[($stackPtr - 1)]) === true
3,072✔
2129
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
3,072✔
2130
                && $tokenIsArray === true
3,072✔
2131
                && $token[0] !== T_STRING
3,072✔
2132
                && $token[0] !== T_VARIABLE
3,072✔
2133
                && $token[0] !== T_DOLLAR
3,072✔
2134
                && isset(Tokens::$emptyTokens[$token[0]]) === false
3,072✔
2135
            ) {
2136
                $newToken            = [];
×
2137
                $newToken['code']    = T_STRING;
×
2138
                $newToken['type']    = 'T_STRING';
×
2139
                $newToken['content'] = $token[1];
×
2140
                $finalTokens[$newStackPtr] = $newToken;
×
2141

2142
                $newStackPtr++;
×
2143
                continue;
×
2144
            }
2145

2146
            /*
2147
                Backfill the T_FN token for PHP versions < 7.4.
2148
            */
2149

2150
            if ($tokenIsArray === true
3,072✔
2151
                && $token[0] === T_STRING
3,072✔
2152
                && strtolower($token[1]) === 'fn'
3,072✔
2153
            ) {
2154
                // Modify the original token stack so that
2155
                // future checks (like looking for T_NULLABLE) can
2156
                // detect the T_FN token more easily.
2157
                $tokens[$stackPtr][0] = T_FN;
640✔
2158
                $token[0] = T_FN;
640✔
2159
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
640✔
2160
                    StatusWriter::write("* token $stackPtr changed from T_STRING to T_FN", 2);
×
2161
                }
2162
            }
2163

2164
            /*
2165
                This is a special condition for T_ARRAY tokens used for
2166
                function return types. We want to keep the parenthesis map clean,
2167
                so let's tag these tokens as T_STRING.
2168
            */
2169

2170
            if ($tokenIsArray === true
3,072✔
2171
                && ($token[0] === T_FUNCTION
3,072✔
2172
                || $token[0] === T_FN)
3,072✔
2173
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
3,072✔
2174
            ) {
2175
                // Go looking for the colon to start the return type hint.
2176
                // Start by finding the closing parenthesis of the function.
2177
                $parenthesisStack  = [];
2,340✔
2178
                $parenthesisCloser = false;
2,340✔
2179
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,340✔
2180
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,340✔
2181
                        $parenthesisStack[] = $x;
2,340✔
2182
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,340✔
2183
                        array_pop($parenthesisStack);
2,340✔
2184
                        if (empty($parenthesisStack) === true) {
2,340✔
2185
                            $parenthesisCloser = $x;
2,340✔
2186
                            break;
2,340✔
2187
                        }
2188
                    }
2189
                }
2190

2191
                if ($parenthesisCloser !== false) {
2,340✔
2192
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,340✔
2193
                        if (is_array($tokens[$x]) === false
2,340✔
2194
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
2,340✔
2195
                        ) {
2196
                            // Non-empty content.
2197
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,340✔
2198
                                // Found a use statement, so search ahead for the closing parenthesis.
2199
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
2200
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
2201
                                        continue(2);
57✔
2202
                                    }
2203
                                }
2204
                            }
2205

2206
                            break;
2,340✔
2207
                        }
2208
                    }
2209

2210
                    if (isset($tokens[$x]) === true
2,340✔
2211
                        && is_array($tokens[$x]) === false
2,340✔
2212
                        && $tokens[$x] === ':'
2,340✔
2213
                    ) {
2214
                        // Find the start of the return type.
2215
                        for ($x += 1; $x < $numTokens; $x++) {
1,950✔
2216
                            if (is_array($tokens[$x]) === true
1,950✔
2217
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
1,950✔
2218
                            ) {
2219
                                // Whitespace or comments before the return type.
2220
                                continue;
1,950✔
2221
                            }
2222

2223
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1,950✔
2224
                                // Found a nullable operator, so skip it.
2225
                                // But also convert the token to save the tokenizer
2226
                                // a bit of time later on.
2227
                                $tokens[$x] = [
135✔
2228
                                    T_NULLABLE,
135✔
2229
                                    '?',
135✔
2230
                                ];
90✔
2231

2232
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2233
                                    StatusWriter::write("* token $x changed from ? to T_NULLABLE", 2);
×
2234
                                }
2235

2236
                                continue;
135✔
2237
                            }
2238

2239
                            break;
1,950✔
2240
                        }//end for
2241
                    }//end if
2242
                }//end if
2243
            }//end if
2244

2245
            /*
2246
                PHP doesn't assign a token to goto labels, so we have to.
2247
                These are just string tokens with a single colon after them. Double
2248
                colons are already tokenized and so don't interfere with this check.
2249
                But we do have to account for CASE statements, that look just like
2250
                goto labels.
2251
            */
2252

2253
            if ($tokenIsArray === true
3,072✔
2254
                && $token[0] === T_STRING
3,072✔
2255
                && isset($tokens[($stackPtr + 1)]) === true
3,072✔
2256
                && $tokens[($stackPtr + 1)] === ':'
3,072✔
2257
                && (is_array($tokens[($stackPtr - 1)]) === false
2,507✔
2258
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
3,072✔
2259
            ) {
2260
                $stopTokens = [
914✔
2261
                    T_CASE               => true,
1,371✔
2262
                    T_SEMICOLON          => true,
1,371✔
2263
                    T_OPEN_TAG           => true,
1,371✔
2264
                    T_OPEN_CURLY_BRACKET => true,
1,371✔
2265
                    T_INLINE_THEN        => true,
1,371✔
2266
                    T_ENUM               => true,
1,371✔
2267
                ];
914✔
2268

2269
                for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,371✔
2270
                    if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,371✔
2271
                        break;
1,371✔
2272
                    }
2273
                }
2274

2275
                if ($finalTokens[$x]['code'] !== T_CASE
1,371✔
2276
                    && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,371✔
2277
                    && $finalTokens[$x]['code'] !== T_ENUM
1,371✔
2278
                ) {
2279
                    $finalTokens[$newStackPtr] = [
108✔
2280
                        'content' => $token[1].':',
108✔
2281
                        'code'    => T_GOTO_LABEL,
108✔
2282
                        'type'    => 'T_GOTO_LABEL',
108✔
2283
                    ];
72✔
2284

2285
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
108✔
2286
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_GOTO_LABEL", 2);
×
2287
                        StatusWriter::write('* skipping T_COLON token '.($stackPtr + 1), 2);
×
2288
                    }
2289

2290
                    $newStackPtr++;
108✔
2291
                    $stackPtr++;
108✔
2292
                    continue;
108✔
2293
                }
2294
            }//end if
2295

2296
            /*
2297
                If this token has newlines in its content, split each line up
2298
                and create a new token for each line. We do this so it's easier
2299
                to ascertain where errors occur on a line.
2300
                Note that $token[1] is the token's content.
2301
            */
2302

2303
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
3,072✔
2304
                $tokenLines = explode($this->eolChar, $token[1]);
3,072✔
2305
                $numLines   = count($tokenLines);
3,072✔
2306
                $newToken   = [
2,050✔
2307
                    'type'    => Tokens::tokenName($token[0]),
3,072✔
2308
                    'code'    => $token[0],
3,072✔
2309
                    'content' => '',
3,072✔
2310
                ];
2,050✔
2311

2312
                for ($i = 0; $i < $numLines; $i++) {
3,072✔
2313
                    $newToken['content'] = $tokenLines[$i];
3,072✔
2314
                    if ($i === ($numLines - 1)) {
3,072✔
2315
                        if ($tokenLines[$i] === '') {
3,072✔
2316
                            break;
3,072✔
2317
                        }
2318
                    } else {
2319
                        $newToken['content'] .= $this->eolChar;
3,072✔
2320
                    }
2321

2322
                    $finalTokens[$newStackPtr] = $newToken;
3,072✔
2323
                    $newStackPtr++;
3,072✔
2324
                }
2325
            } else {
2326
                // Some T_STRING tokens should remain that way due to their context.
2327
                if ($tokenIsArray === true && $token[0] === T_STRING) {
3,072✔
2328
                    $preserveTstring = false;
2,670✔
2329

2330
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2331
                    // but the constant name should not be.
2332
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,670✔
2333
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,556✔
2334
                        || $insideConstDeclaration === true
2,670✔
2335
                    ) {
2336
                        // Find the next non-empty token.
2337
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,615✔
2338
                            if (is_array($tokens[$i]) === true
1,615✔
2339
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,615✔
2340
                            ) {
2341
                                continue;
1,480✔
2342
                            }
2343

2344
                            break;
1,615✔
2345
                        }
2346

2347
                        if ($tokens[$i] === '=') {
1,615✔
2348
                            $preserveTstring        = true;
1,426✔
2349
                            $insideConstDeclaration = false;
1,552✔
2350
                        }
2351
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,670✔
2352
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,670✔
2353
                    ) {
2354
                        $preserveTstring = true;
2,556✔
2355

2356
                        // Special case for syntax like: return new self/new parent
2357
                        // where self/parent should not be a string.
2358
                        $tokenContentLower = strtolower($token[1]);
2,556✔
2359
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,556✔
2360
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,556✔
2361
                        ) {
2362
                            $preserveTstring = false;
2,077✔
2363
                        }
2364
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,640✔
2365
                        // Function names for functions declared to return by reference.
2366
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,047✔
2367
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,047✔
2368
                                continue;
480✔
2369
                            }
2370

2371
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,047✔
2372
                                $preserveTstring = true;
480✔
2373
                            }
2374

2375
                            break;
1,047✔
2376
                        }
2377
                    } else {
2378
                        // Keywords with special PHPCS token when used as a function call.
2379
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,640✔
2380
                            if (is_array($tokens[$i]) === true
2,640✔
2381
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,640✔
2382
                            ) {
2383
                                continue;
2,235✔
2384
                            }
2385

2386
                            if ($tokens[$i][0] === '(') {
2,640✔
2387
                                $preserveTstring = true;
2,214✔
2388
                            }
2389

2390
                            break;
2,640✔
2391
                        }
2392
                    }//end if
2393

2394
                    if ($preserveTstring === true) {
2,670✔
2395
                        $finalTokens[$newStackPtr] = [
2,559✔
2396
                            'code'    => T_STRING,
2,559✔
2397
                            'type'    => 'T_STRING',
2,559✔
2398
                            'content' => $token[1],
2,559✔
2399
                        ];
1,706✔
2400

2401
                        $newStackPtr++;
2,559✔
2402
                        continue;
2,559✔
2403
                    }
2404
                }//end if
2405

2406
                $newToken = null;
3,072✔
2407
                if ($tokenIsArray === false) {
3,072✔
2408
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3,060✔
2409
                        $newToken = self::$resolveTokenCache[$token[0]];
3,060✔
2410
                    }
2411
                } else {
2412
                    $cacheKey = null;
3,072✔
2413
                    if ($token[0] === T_STRING) {
3,072✔
2414
                        $cacheKey = strtolower($token[1]);
2,637✔
2415
                    } else if ($token[0] !== T_CURLY_OPEN) {
3,072✔
2416
                        $cacheKey = $token[0];
3,072✔
2417
                    }
2418

2419
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,072✔
2420
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,072✔
2421
                        $newToken['content'] = $token[1];
3,072✔
2422
                    }
2423
                }
2424

2425
                if ($newToken === null) {
3,072✔
2426
                    $newToken = self::standardiseToken($token);
18✔
2427
                }
2428

2429
                // Convert colons that are actually the ELSE component of an
2430
                // inline IF statement.
2431
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
3,072✔
2432
                    $isInlineIf = true;
1,077✔
2433

2434
                    // Make sure this isn't a named parameter label.
2435
                    // Get the previous non-empty token.
2436
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,077✔
2437
                        if (is_array($tokens[$i]) === false
1,077✔
2438
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,077✔
2439
                        ) {
2440
                            break;
1,077✔
2441
                        }
2442
                    }
2443

2444
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,077✔
2445
                        $isInlineIf = false;
639✔
2446
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2447
                            StatusWriter::write('* token is parameter label, not T_INLINE_ELSE', 2);
×
2448
                        }
2449
                    }
2450

2451
                    if ($isInlineIf === true) {
1,077✔
2452
                        // Make sure this isn't a return type separator.
2453
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,077✔
2454
                            if (is_array($tokens[$i]) === false
1,077✔
2455
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,077✔
2456
                                && $tokens[$i][0] !== T_COMMENT
1,077✔
2457
                                && $tokens[$i][0] !== T_WHITESPACE)
1,077✔
2458
                            ) {
2459
                                break;
1,077✔
2460
                            }
2461
                        }
2462

2463
                        if ($tokens[$i] === ')') {
1,077✔
2464
                            $parenCount = 1;
639✔
2465
                            for ($i--; $i > 0; $i--) {
639✔
2466
                                if ($tokens[$i] === '(') {
639✔
2467
                                    $parenCount--;
639✔
2468
                                    if ($parenCount === 0) {
639✔
2469
                                        break;
639✔
2470
                                    }
2471
                                } else if ($tokens[$i] === ')') {
639✔
2472
                                    $parenCount++;
×
2473
                                }
2474
                            }
2475

2476
                            // We've found the open parenthesis, so if the previous
2477
                            // non-empty token is FUNCTION or USE, this is a return type.
2478
                            // Note that we need to skip T_STRING tokens here as these
2479
                            // can be function names.
2480
                            for ($i--; $i > 0; $i--) {
639✔
2481
                                if (is_array($tokens[$i]) === false
639✔
2482
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
639✔
2483
                                    && $tokens[$i][0] !== T_COMMENT
639✔
2484
                                    && $tokens[$i][0] !== T_WHITESPACE
639✔
2485
                                    && $tokens[$i][0] !== T_STRING)
639✔
2486
                                ) {
2487
                                    break;
639✔
2488
                                }
2489
                            }
2490

2491
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
639✔
2492
                                $isInlineIf = false;
639✔
2493
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2494
                                    StatusWriter::write('* token is return type, not T_INLINE_ELSE', 2);
×
2495
                                }
2496
                            }
2497
                        }//end if
2498
                    }//end if
2499

2500
                    // Check to see if this is a CASE or DEFAULT opener.
2501
                    if ($isInlineIf === true) {
1,077✔
2502
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,077✔
2503
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,077✔
2504
                            if (is_array($tokens[$i]) === true
1,077✔
2505
                                && ($tokens[$i][0] === T_CASE
1,077✔
2506
                                || $tokens[$i][0] === T_DEFAULT)
1,077✔
2507
                            ) {
2508
                                $isInlineIf = false;
×
2509
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2510
                                    StatusWriter::write('* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE', 2);
×
2511
                                }
2512

2513
                                break;
×
2514
                            }
2515

2516
                            if (is_array($tokens[$i]) === false
1,077✔
2517
                                && ($tokens[$i] === ';'
1,077✔
2518
                                || $tokens[$i] === '{'
1,077✔
2519
                                || $tokens[$i] === '}')
1,077✔
2520
                            ) {
2521
                                break;
822✔
2522
                            }
2523
                        }//end for
2524
                    }//end if
2525

2526
                    if ($isInlineIf === true) {
1,077✔
2527
                        array_pop($insideInlineIf);
1,077✔
2528
                        $newToken['code'] = T_INLINE_ELSE;
1,077✔
2529
                        $newToken['type'] = 'T_INLINE_ELSE';
1,077✔
2530

2531
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,077✔
2532
                            StatusWriter::write('* token changed from T_COLON to T_INLINE_ELSE', 2);
×
2533
                        }
2534
                    }
2535
                }//end if
2536

2537
                // This is a special condition for T_ARRAY tokens used for anything else
2538
                // but array declarations, like type hinting function arguments as
2539
                // being arrays.
2540
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2541
                // T_STRING.
2542
                if ($newToken['code'] === T_ARRAY) {
3,072✔
2543
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,518✔
2544
                        if (is_array($tokens[$i]) === false
1,518✔
2545
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,518✔
2546
                        ) {
2547
                            // Non-empty content.
2548
                            break;
1,518✔
2549
                        }
2550
                    }
2551

2552
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,518✔
2553
                        $newToken['code'] = T_STRING;
1,287✔
2554
                        $newToken['type'] = 'T_STRING';
1,287✔
2555
                    }
2556
                }
2557

2558
                // This is a special case for PHP 5.6 use function and use const
2559
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2560
                // and T_CONST.
2561
                if (($newToken['code'] === T_FUNCTION
3,072✔
2562
                    || $newToken['code'] === T_CONST)
3,072✔
2563
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
3,072✔
2564
                ) {
2565
                    $newToken['code'] = T_STRING;
135✔
2566
                    $newToken['type'] = 'T_STRING';
135✔
2567
                }
2568

2569
                // This is a special case for use groups in PHP 7+ where leaving
2570
                // the curly braces as their normal tokens would confuse
2571
                // the scope map and sniffs.
2572
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
3,072✔
2573
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
3,072✔
2574
                ) {
2575
                    $newToken['code'] = T_OPEN_USE_GROUP;
135✔
2576
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
135✔
2577
                    $insideUseGroup   = true;
135✔
2578
                }
2579

2580
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,072✔
2581
                    $newToken['code'] = T_CLOSE_USE_GROUP;
135✔
2582
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
135✔
2583
                    $insideUseGroup   = false;
135✔
2584
                }
2585

2586
                $finalTokens[$newStackPtr] = $newToken;
3,072✔
2587
                $newStackPtr++;
3,072✔
2588
            }//end if
2589
        }//end for
2590

2591
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,072✔
2592
            StatusWriter::write('*** END PHP TOKENIZING ***', 1);
×
2593
        }
2594

2595
        return $finalTokens;
3,072✔
2596

2597
    }//end tokenize()
2598

2599

2600
    /**
2601
     * Performs additional processing after main tokenizing.
2602
     *
2603
     * This additional processing checks for CASE statements that are using curly
2604
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2605
     * into T_CLOSURE when they are not standard function definitions. It also
2606
     * detects short array syntax and converts those square brackets into new tokens.
2607
     * It also corrects some usage of the static and class keywords. It also
2608
     * assigns tokens to function return types.
2609
     *
2610
     * @return void
2611
     */
2612
    protected function processAdditional()
1,707✔
2613
    {
2614
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,707✔
2615
            StatusWriter::write('*** START ADDITIONAL PHP PROCESSING ***', 1);
×
2616
        }
2617

2618
        $this->createAttributesNestingMap();
1,707✔
2619

2620
        $numTokens         = count($this->tokens);
1,707✔
2621
        $lastSeenTypeToken = $numTokens;
1,707✔
2622

2623
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,707✔
2624
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2625
            if (isset($this->tokens[$i]['scope_opener']) === true
1,707✔
2626
                && isset($this->tokens[$i]['scope_condition']) === false
1,707✔
2627
            ) {
2628
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2629
            }
2630

2631
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,707✔
2632
                /*
2633
                    Detect functions that are actually closures and
2634
                    assign them a different token.
2635
                */
2636

2637
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,428✔
2638
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,428✔
2639
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,428✔
2640
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,428✔
2641
                        ) {
2642
                            break;
1,428✔
2643
                        }
2644
                    }
2645

2646
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,428✔
2647
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,053✔
2648
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,053✔
2649
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,053✔
2650
                            $line = $this->tokens[$i]['line'];
×
2651
                            StatusWriter::write("* token $i on line $line changed from T_FUNCTION to T_CLOSURE", 1);
×
2652
                        }
2653

2654
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,053✔
2655
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
642✔
2656
                                continue;
×
2657
                            }
2658

2659
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
642✔
2660
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
642✔
2661
                                $type = $this->tokens[$x]['type'];
×
2662
                                StatusWriter::write("* cleaned $x ($type) *", 2);
×
2663
                            }
2664
                        }
2665
                    }
2666
                }//end if
2667

2668
                continue;
1,428✔
2669
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,707✔
2670
                /*
2671
                    Detect anonymous classes and assign them a different token.
2672
                */
2673

2674
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,458✔
2675
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,458✔
2676
                        break;
1,458✔
2677
                    }
2678
                }
2679

2680
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,458✔
2681
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,458✔
2682
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,431✔
2683
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,458✔
2684
                ) {
2685
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,068✔
2686
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,068✔
2687
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,068✔
2688
                        $line = $this->tokens[$i]['line'];
×
2689
                        StatusWriter::write("* token $i on line $line changed from T_CLASS to T_ANON_CLASS", 1);
×
2690
                    }
2691

2692
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,068✔
2693
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,068✔
2694
                    ) {
2695
                        $closer = $this->tokens[$x]['parenthesis_closer'];
657✔
2696

2697
                        $this->tokens[$i]['parenthesis_opener']     = $x;
657✔
2698
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
657✔
2699
                        $this->tokens[$i]['parenthesis_owner']      = $i;
657✔
2700
                        $this->tokens[$x]['parenthesis_owner']      = $i;
657✔
2701
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
657✔
2702

2703
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
657✔
2704
                            $line = $this->tokens[$i]['line'];
×
2705
                            StatusWriter::write("* added parenthesis keys to T_ANON_CLASS token $i on line $line", 2);
×
2706
                        }
2707
                    }
2708

2709
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,068✔
2710
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,068✔
2711
                            continue;
×
2712
                        }
2713

2714
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,068✔
2715
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,068✔
2716
                            $type = $this->tokens[$x]['type'];
×
2717
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
2718
                        }
2719
                    }
2720
                }//end if
2721

2722
                continue;
1,458✔
2723
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,707✔
2724
                // Possible arrow function.
2725
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,137✔
2726
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,137✔
2727
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,137✔
2728
                    ) {
2729
                        // Non-whitespace content.
2730
                        break;
1,137✔
2731
                    }
2732
                }
2733

2734
                if (isset($this->tokens[$x]) === true
1,137✔
2735
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,137✔
2736
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,137✔
2737
                ) {
2738
                    $ignore  = Tokens::$emptyTokens;
1,134✔
2739
                    $ignore += [
756✔
2740
                        T_ARRAY                  => T_ARRAY,
1,134✔
2741
                        T_CALLABLE               => T_CALLABLE,
1,134✔
2742
                        T_COLON                  => T_COLON,
1,134✔
2743
                        T_NAMESPACE              => T_NAMESPACE,
1,134✔
2744
                        T_NS_SEPARATOR           => T_NS_SEPARATOR,
1,134✔
2745
                        T_NULL                   => T_NULL,
1,134✔
2746
                        T_TRUE                   => T_TRUE,
1,134✔
2747
                        T_FALSE                  => T_FALSE,
1,134✔
2748
                        T_NULLABLE               => T_NULLABLE,
1,134✔
2749
                        T_PARENT                 => T_PARENT,
1,134✔
2750
                        T_SELF                   => T_SELF,
1,134✔
2751
                        T_STATIC                 => T_STATIC,
1,134✔
2752
                        T_STRING                 => T_STRING,
1,134✔
2753
                        T_TYPE_UNION             => T_TYPE_UNION,
1,134✔
2754
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,134✔
2755
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,134✔
2756
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,134✔
2757
                    ];
756✔
2758

2759
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,134✔
2760
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,134✔
2761
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,134✔
2762
                            break;
1,134✔
2763
                        }
2764
                    }
2765

2766
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,134✔
2767
                        $endTokens = [
756✔
2768
                            T_COLON                => true,
1,134✔
2769
                            T_COMMA                => true,
1,134✔
2770
                            T_SEMICOLON            => true,
1,134✔
2771
                            T_CLOSE_PARENTHESIS    => true,
1,134✔
2772
                            T_CLOSE_SQUARE_BRACKET => true,
1,134✔
2773
                            T_CLOSE_CURLY_BRACKET  => true,
1,134✔
2774
                            T_CLOSE_SHORT_ARRAY    => true,
1,134✔
2775
                            T_OPEN_TAG             => true,
1,134✔
2776
                            T_CLOSE_TAG            => true,
1,134✔
2777
                        ];
756✔
2778

2779
                        $inTernary    = false;
1,134✔
2780
                        $lastEndToken = null;
1,134✔
2781

2782
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,134✔
2783
                            // Arrow function closer should never be shared with the closer of a match
2784
                            // control structure.
2785
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,134✔
2786
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,134✔
2787
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,134✔
2788
                            ) {
2789
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
423✔
2790
                                    // Match in return value of arrow function. Move on to the next token.
2791
                                    continue;
423✔
2792
                                }
2793

2794
                                // Arrow function as return value for the last match case without trailing comma.
2795
                                if ($lastEndToken !== null) {
423✔
2796
                                    $scopeCloser = $lastEndToken;
423✔
2797
                                    break;
423✔
2798
                                }
2799

2800
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2801
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2802
                                        $scopeCloser = $lastNonEmpty;
186✔
2803
                                        break 2;
186✔
2804
                                    }
2805
                                }
2806
                            }
2807

2808
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,134✔
2809
                                if ($lastEndToken !== null
1,134✔
2810
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
997✔
2811
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
818✔
2812
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
997✔
2813
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
1,134✔
2814
                                ) {
2815
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2816
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2817
                                            $scopeCloser = $lastNonEmpty;
186✔
2818
                                            break;
186✔
2819
                                        }
2820
                                    }
2821
                                }
2822

2823
                                break;
1,134✔
2824
                            }
2825

2826
                            if ($inTernary === false
1,134✔
2827
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,134✔
2828
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,134✔
2829
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,134✔
2830
                            ) {
2831
                                // Found a nested arrow function that already has the closer set and is in
2832
                                // the same scope as us, so we can use its closer.
2833
                                break;
186✔
2834
                            }
2835

2836
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,134✔
2837
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,134✔
2838
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,134✔
2839
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,134✔
2840
                            ) {
2841
                                // We minus 1 here in case the closer can be shared with us.
2842
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
423✔
2843
                                continue;
423✔
2844
                            }
2845

2846
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,134✔
2847
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
723✔
2848
                                $lastEndToken = $scopeCloser;
723✔
2849
                                continue;
723✔
2850
                            }
2851

2852
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,134✔
2853
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
186✔
2854
                                $lastEndToken = $scopeCloser;
186✔
2855
                                continue;
186✔
2856
                            }
2857

2858
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,134✔
2859
                                $inTernary = true;
186✔
2860
                                continue;
186✔
2861
                            }
2862

2863
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,134✔
2864
                                if ($inTernary === false) {
186✔
2865
                                    break;
186✔
2866
                                }
2867

2868
                                $inTernary = false;
186✔
2869
                                continue;
186✔
2870
                            }
2871
                        }//end for
2872

2873
                        if ($scopeCloser !== $numTokens) {
1,134✔
2874
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,134✔
2875
                                $line = $this->tokens[$i]['line'];
×
2876
                                StatusWriter::write("=> token $i on line $line processed as arrow function", 1);
×
2877
                                StatusWriter::write("* scope opener set to $arrow *", 2);
×
2878
                                StatusWriter::write("* scope closer set to $scopeCloser *", 2);
×
2879
                                StatusWriter::write("* parenthesis opener set to $x *", 2);
×
2880
                                StatusWriter::write("* parenthesis closer set to $closer *", 2);
×
2881
                            }
2882

2883
                            $this->tokens[$i]['code']            = T_FN;
1,134✔
2884
                            $this->tokens[$i]['type']            = 'T_FN';
1,134✔
2885
                            $this->tokens[$i]['scope_condition'] = $i;
1,134✔
2886
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,134✔
2887
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,134✔
2888
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,134✔
2889
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,134✔
2890
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,134✔
2891

2892
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,134✔
2893
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,134✔
2894

2895
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,134✔
2896
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,134✔
2897
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,134✔
2898
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,134✔
2899
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,134✔
2900
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,134✔
2901

2902
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,134✔
2903
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,134✔
2904
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,134✔
2905
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,134✔
2906

2907
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,134✔
2908
                                $line = $this->tokens[$arrow]['line'];
×
2909
                                StatusWriter::write("* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW", 2);
×
2910
                            }
2911
                        }//end if
2912
                    }//end if
2913
                }//end if
2914

2915
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
2916
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,137✔
2917
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
600✔
2918
                        $line = $this->tokens[$i]['line'];
×
2919
                        StatusWriter::write("=> token $i on line $line is not an arrow function", 1);
×
2920
                        StatusWriter::write('* token changed from T_FN to T_STRING', 2);
×
2921
                    }
2922

2923
                    $this->tokens[$i]['code'] = T_STRING;
600✔
2924
                    $this->tokens[$i]['type'] = 'T_STRING';
958✔
2925
                }
2926
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,707✔
2927
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,164✔
2928
                    continue;
99✔
2929
                }
2930

2931
                // Unless there is a variable or a bracket before this token,
2932
                // it is the start of an array being defined using the short syntax.
2933
                $isShortArray = false;
1,164✔
2934
                $allowed      = [
776✔
2935
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,164✔
2936
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,164✔
2937
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,164✔
2938
                    T_VARIABLE                 => T_VARIABLE,
1,164✔
2939
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,164✔
2940
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,164✔
2941
                    T_STRING                   => T_STRING,
1,164✔
2942
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,164✔
2943
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,164✔
2944
                ];
776✔
2945
                $allowed     += Tokens::$magicConstants;
1,164✔
2946

2947
                for ($x = ($i - 1); $x >= 0; $x--) {
1,164✔
2948
                    // If we hit a scope opener, the statement has ended
2949
                    // without finding anything, so it's probably an array
2950
                    // using PHP 7.1 short list syntax.
2951
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,164✔
2952
                        $isShortArray = true;
234✔
2953
                        break;
234✔
2954
                    }
2955

2956
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,164✔
2957
                        // Allow for control structures without braces.
2958
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,164✔
2959
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,164✔
2960
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
99✔
2961
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,164✔
2962
                        ) {
2963
                            $isShortArray = true;
1,164✔
2964
                        }
2965

2966
                        break;
1,164✔
2967
                    }
2968
                }//end for
2969

2970
                if ($isShortArray === true) {
1,164✔
2971
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,164✔
2972
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,164✔
2973

2974
                    $closer = $this->tokens[$i]['bracket_closer'];
1,164✔
2975
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,164✔
2976
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,164✔
2977
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,164✔
2978
                        $line = $this->tokens[$i]['line'];
×
2979
                        StatusWriter::write("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
2980
                        $line = $this->tokens[$closer]['line'];
×
2981
                        StatusWriter::write("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
2982
                    }
2983
                }
2984

2985
                continue;
1,164✔
2986
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,707✔
2987
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
777✔
2988
                    // Not a match expression after all.
2989
                    $this->tokens[$i]['code'] = T_STRING;
102✔
2990
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
2991

2992
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
2993
                        StatusWriter::write("* token $i changed from T_MATCH to T_STRING", 2);
×
2994
                    }
2995

2996
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
2997
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
2998
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
2999
                        unset(
68✔
3000
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
3001
                            $this->tokens[$closer]['parenthesis_owner']
102✔
3002
                        );
68✔
3003
                        unset(
68✔
3004
                            $this->tokens[$i]['parenthesis_opener'],
102✔
3005
                            $this->tokens[$i]['parenthesis_closer'],
102✔
3006
                            $this->tokens[$i]['parenthesis_owner']
102✔
3007
                        );
68✔
3008

3009
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3010
                            StatusWriter::write("* cleaned parenthesis of token $i *", 2);
68✔
3011
                        }
3012
                    }
3013
                } else {
3014
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3015
                    $searchFor  = [
518✔
3016
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
777✔
3017
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
777✔
3018
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
777✔
3019
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
777✔
3020
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
777✔
3021
                    ];
518✔
3022
                    $searchFor += Tokens::$scopeOpeners;
777✔
3023

3024
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
777✔
3025
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
777✔
3026
                            continue;
777✔
3027
                        }
3028

3029
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
477✔
3030
                            $x = $this->tokens[$x]['scope_closer'];
291✔
3031
                            continue;
291✔
3032
                        }
3033

3034
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
477✔
3035
                            $x = $this->tokens[$x]['parenthesis_closer'];
477✔
3036
                            continue;
477✔
3037
                        }
3038

3039
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
477✔
3040
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
3041
                            continue;
291✔
3042
                        }
3043

3044
                        // This must be a double arrow, but make sure anyhow.
3045
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
477✔
3046
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
477✔
3047
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
477✔
3048

3049
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
477✔
3050
                                StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
3051
                            }
3052
                        }
3053
                    }//end for
3054
                }//end if
3055

3056
                continue;
777✔
3057
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,707✔
3058
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,707✔
3059
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,707✔
3060
            ) {
3061
                if ($lastSeenTypeToken < $i) {
1,704✔
3062
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3063
                    // No need to do it again.
3064
                    continue;
849✔
3065
                }
3066

3067
                /*
3068
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3069
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3070
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3071

3072
                    All type related tokens will be converted in one go as soon as this section is hit.
3073
                */
3074

3075
                $allowed = [
1,136✔
3076
                    T_STRING       => T_STRING,
1,704✔
3077
                    T_CALLABLE     => T_CALLABLE,
1,704✔
3078
                    T_SELF         => T_SELF,
1,704✔
3079
                    T_PARENT       => T_PARENT,
1,704✔
3080
                    T_STATIC       => T_STATIC,
1,704✔
3081
                    T_FALSE        => T_FALSE,
1,704✔
3082
                    T_TRUE         => T_TRUE,
1,704✔
3083
                    T_NULL         => T_NULL,
1,704✔
3084
                    T_NAMESPACE    => T_NAMESPACE,
1,704✔
3085
                    T_NS_SEPARATOR => T_NS_SEPARATOR,
1,704✔
3086
                ];
1,136✔
3087

3088
                $suspectedType       = null;
1,704✔
3089
                $typeTokenCountAfter = 0;
1,704✔
3090

3091
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,704✔
3092
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,704✔
3093
                        continue;
1,704✔
3094
                    }
3095

3096
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,704✔
3097
                        ++$typeTokenCountAfter;
1,275✔
3098
                        continue;
1,275✔
3099
                    }
3100

3101
                    if (($typeTokenCountAfter > 0
1,704✔
3102
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,704✔
3103
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,704✔
3104
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,704✔
3105
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,704✔
3106
                    ) {
3107
                        // Skip past reference and variadic indicators for parameter types.
3108
                        continue;
912✔
3109
                    }
3110

3111
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,704✔
3112
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3113
                        $suspectedType = 'property or parameter';
1,098✔
3114
                        break;
1,098✔
3115
                    }
3116

3117
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,704✔
3118
                        // Possible arrow function.
3119
                        $suspectedType = 'return';
1,134✔
3120
                        break;
1,134✔
3121
                    }
3122

3123
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,704✔
3124
                        // Possible abstract method or interface method.
3125
                        $suspectedType = 'return';
1,629✔
3126
                        break;
1,629✔
3127
                    }
3128

3129
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,704✔
3130
                        && isset($this->tokens[$x]['scope_condition']) === true
1,704✔
3131
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,704✔
3132
                    ) {
3133
                        $suspectedType = 'return';
1,416✔
3134
                        break;
1,416✔
3135
                    }
3136

3137
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,704✔
3138
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3139
                        $suspectedType = 'constant';
1,059✔
3140
                        break;
1,059✔
3141
                    }
3142

3143
                    break;
1,704✔
3144
                }//end for
3145

3146
                if (($typeTokenCountAfter === 0
1,704✔
3147
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,704✔
3148
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,704✔
3149
                    || isset($suspectedType) === false
1,704✔
3150
                ) {
3151
                    // Definitely not a union, intersection or DNF type, move on.
3152
                    continue;
1,704✔
3153
                }
3154

3155
                if ($suspectedType === 'property or parameter') {
1,665✔
3156
                    unset($allowed[T_STATIC]);
1,098✔
3157
                }
3158

3159
                $typeTokenCountBefore = 0;
1,665✔
3160
                $typeOperators        = [$i];
1,665✔
3161
                $parenthesesCount     = 0;
1,665✔
3162
                $confirmed            = false;
1,665✔
3163
                $maybeNullable        = null;
1,665✔
3164

3165
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,665✔
3166
                    ++$parenthesesCount;
1,665✔
3167
                }
3168

3169
                for ($x = ($i - 1); $x >= 0; $x--) {
1,665✔
3170
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,665✔
3171
                        continue;
1,377✔
3172
                    }
3173

3174
                    if ($suspectedType === 'property or parameter'
1,665✔
3175
                        && $this->tokens[$x]['code'] === T_STRING
1,665✔
3176
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,665✔
3177
                    ) {
3178
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3179
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3180
                        $this->tokens[$x]['code'] = T_STATIC;
300✔
3181
                        $this->tokens[$x]['type'] = 'T_STATIC';
300✔
3182

3183
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3184
                            $line = $this->tokens[$x]['line'];
×
3185
                            StatusWriter::write("* token $x on line $line changed back from T_STRING to T_STATIC", 1);
×
3186
                        }
3187
                    }
3188

3189
                    if ($suspectedType === 'property or parameter'
1,665✔
3190
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,665✔
3191
                    ) {
3192
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3193
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3194
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,098✔
3195
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,098✔
3196
                        ) {
3197
                            $confirmed = true;
1,036✔
3198
                            break;
1,036✔
3199
                        } else {
3200
                            // This may still be an arrow function which hasn't been handled yet.
3201
                            for ($y = ($x - 1); $y > 0; $y--) {
1,098✔
3202
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
1,098✔
3203
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,098✔
3204
                                ) {
3205
                                    // Non-whitespace content.
3206
                                    break;
1,098✔
3207
                                }
3208
                            }
3209

3210
                            if ($this->tokens[$y]['code'] === T_FN) {
1,098✔
3211
                                $confirmed = true;
897✔
3212
                                break;
897✔
3213
                            }
3214
                        }
3215
                    }//end if
3216

3217
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,665✔
3218
                        ++$typeTokenCountBefore;
1,377✔
3219
                        continue;
1,377✔
3220
                    }
3221

3222
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3223
                    if (($typeTokenCountBefore > 0
1,665✔
3224
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,665✔
3225
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,569✔
3226
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,665✔
3227
                    ) {
3228
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
888✔
3229
                            $maybeNullable = $x;
300✔
3230
                        }
3231

3232
                        continue;
888✔
3233
                    }
3234

3235
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,665✔
3236
                        $typeOperators[] = $x;
1,275✔
3237
                        continue;
1,275✔
3238
                    }
3239

3240
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,665✔
3241
                        ++$parenthesesCount;
1,377✔
3242
                        $typeOperators[] = $x;
1,377✔
3243
                        continue;
1,377✔
3244
                    }
3245

3246
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,665✔
3247
                        // Make sure this is the colon for a return type.
3248
                        for ($y = ($x - 1); $y > 0; $y--) {
933✔
3249
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
933✔
3250
                                break;
933✔
3251
                            }
3252
                        }
3253

3254
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
933✔
3255
                            // Definitely not a union, intersection or DNF return type, move on.
3256
                            continue 2;
300✔
3257
                        }
3258

3259
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
933✔
3260
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
835✔
3261
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
300✔
3262
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
300✔
3263
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_USE
835✔
3264
                            ) {
3265
                                $confirmed = true;
835✔
3266
                            }
3267

3268
                            break;
835✔
3269
                        }
3270

3271
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3272
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
933✔
3273
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
897✔
3274
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
897✔
3275
                                    break;
897✔
3276
                                }
3277
                            }
3278

3279
                            if ($this->tokens[$z]['code'] === T_FN) {
897✔
3280
                                $confirmed = true;
897✔
3281
                            }
3282
                        }
3283

3284
                        break;
933✔
3285
                    }//end if
3286

3287
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,665✔
3288
                        $confirmed = true;
924✔
3289
                        break;
924✔
3290
                    }
3291

3292
                    if ($suspectedType === 'property or parameter'
1,665✔
3293
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,414✔
3294
                        || $this->tokens[$x]['code'] === T_VAR
1,402✔
3295
                        || $this->tokens[$x]['code'] === T_STATIC
1,402✔
3296
                        || $this->tokens[$x]['code'] === T_READONLY
1,402✔
3297
                        || $this->tokens[$x]['code'] === T_FINAL)
1,665✔
3298
                    ) {
3299
                        // This will also confirm constructor property promotion parameters, but that's fine.
3300
                        $confirmed = true;
912✔
3301
                    }
3302

3303
                    break;
1,665✔
3304
                }//end for
3305

3306
                // Remember the last token we examined as part of the (non-)"type declaration".
3307
                $lastSeenTypeToken = $x;
1,665✔
3308

3309
                if ($confirmed === false
1,665✔
3310
                    && $suspectedType === 'property or parameter'
1,665✔
3311
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,665✔
3312
                ) {
3313
                    $parens = $this->tokens[$i]['nested_parenthesis'];
711✔
3314
                    $last   = end($parens);
711✔
3315

3316
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
711✔
3317
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
711✔
3318
                    ) {
3319
                        $confirmed = true;
711✔
3320
                    } else {
3321
                        // No parenthesis owner set, this may be an arrow function which has not yet
3322
                        // had additional processing done.
3323
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
213✔
3324
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
213✔
3325
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
213✔
3326
                                    continue;
213✔
3327
                                }
3328

3329
                                break;
213✔
3330
                            }
3331

3332
                            if ($this->tokens[$x]['code'] === T_FN) {
213✔
3333
                                for (--$x; $x >= 0; $x--) {
×
3334
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
3335
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3336
                                    ) {
3337
                                        continue;
×
3338
                                    }
3339

3340
                                    break;
×
3341
                                }
3342

3343
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3344
                                    $confirmed = true;
×
3345
                                }
3346
                            }
3347
                        }//end if
3348
                    }//end if
3349

3350
                    unset($parens, $last);
711✔
3351
                }//end if
3352

3353
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,665✔
3354
                    // Not a (valid) union, intersection or DNF type after all, move on.
3355
                    continue;
1,665✔
3356
                }
3357

3358
                foreach ($typeOperators as $x) {
1,239✔
3359
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,239✔
3360
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,239✔
3361
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,239✔
3362

3363
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3364
                            $line = $this->tokens[$x]['line'];
×
3365
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
826✔
3366
                        }
3367
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,239✔
3368
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,239✔
3369
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,239✔
3370

3371
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3372
                            $line = $this->tokens[$x]['line'];
×
3373
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
826✔
3374
                        }
3375
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,239✔
3376
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,239✔
3377
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,239✔
3378

3379
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3380
                            $line = $this->tokens[$x]['line'];
×
3381
                            StatusWriter::write("* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS", 1);
826✔
3382
                        }
3383
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,239✔
3384
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,239✔
3385
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,239✔
3386

3387
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3388
                            $line = $this->tokens[$x]['line'];
×
3389
                            StatusWriter::write("* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS", 1);
×
3390
                        }
3391
                    }//end if
3392
                }//end foreach
3393

3394
                if (isset($maybeNullable) === true) {
1,239✔
3395
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
300✔
3396
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
300✔
3397

3398
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3399
                        $line = $this->tokens[$maybeNullable]['line'];
×
3400
                        StatusWriter::write("* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE", 1);
×
3401
                    }
3402
                }
3403

3404
                continue;
1,239✔
3405
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,707✔
3406
                || $this->tokens[$i]['code'] === T_FALSE
1,707✔
3407
                || $this->tokens[$i]['code'] === T_NULL
1,707✔
3408
            ) {
3409
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,623✔
3410
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,623✔
3411
                        // Non-whitespace content.
3412
                        break;
1,623✔
3413
                    }
3414
                }
3415

3416
                if ($x !== $numTokens
1,623✔
3417
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,623✔
3418
                ) {
3419
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3420
                        $line = $this->tokens[$i]['line'];
×
3421
                        $type = $this->tokens[$i]['type'];
×
3422
                        StatusWriter::write("* token $i on line $line changed from $type to T_STRING", 1);
×
3423
                    }
3424

3425
                    $this->tokens[$i]['code'] = T_STRING;
×
3426
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3427
                }
3428
            }//end if
3429

3430
            if (($this->tokens[$i]['code'] !== T_CASE
1,707✔
3431
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,707✔
3432
                || isset($this->tokens[$i]['scope_opener']) === false
1,707✔
3433
            ) {
3434
                // Only interested in CASE and DEFAULT statements from here on in.
3435
                continue;
1,707✔
3436
            }
3437

3438
            $scopeOpener = $this->tokens[$i]['scope_opener'];
456✔
3439
            $scopeCloser = $this->tokens[$i]['scope_closer'];
456✔
3440

3441
            // If the first char after the opener is a curly brace
3442
            // and that brace has been ignored, it is actually
3443
            // opening this case statement and the opener and closer are
3444
            // probably set incorrectly.
3445
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
456✔
3446
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
456✔
3447
                    // Non-whitespace content.
3448
                    break;
456✔
3449
                }
3450
            }
3451

3452
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
456✔
3453
                // Special case for multiple CASE statements that share the same
3454
                // closer. Because we are going backwards through the file, this next
3455
                // CASE statement is already fixed, so just use its closer and don't
3456
                // worry about fixing anything.
3457
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3458
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3459
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3460
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3461
                    $newType = $this->tokens[$newCloser]['type'];
×
3462
                    $line    = $this->tokens[$i]['line'];
×
3463
                    StatusWriter::write("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3464
                }
3465

3466
                continue;
×
3467
            }
3468

3469
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
456✔
3470
                || isset($this->tokens[$x]['scope_condition']) === true
456✔
3471
            ) {
3472
                // Not a CASE/DEFAULT with a curly brace opener.
3473
                continue;
456✔
3474
            }
3475

3476
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3477
            // not whatever it already is. The opener needs to be the opening curly
3478
            // brace so everything matches up.
3479
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3480
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3481
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3482
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3483
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3484
            }
3485

3486
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3487
                $line      = $this->tokens[$i]['line'];
×
3488
                $tokenType = $this->tokens[$i]['type'];
×
3489

3490
                $oldType = $this->tokens[$scopeOpener]['type'];
×
3491
                $newType = $this->tokens[$x]['type'];
×
3492
                StatusWriter::write("* token $i ($tokenType) on line $line opener changed from $scopeOpener ($oldType) to $x ($newType)", 1);
×
3493

3494
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3495
                $newType = $this->tokens[$newCloser]['type'];
×
3496
                StatusWriter::write("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3497
            }
3498

3499
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3500
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3501
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3502
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3503
            }
3504

3505
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3506
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3507
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3508
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3509
            } else {
3510
                // We were using a shared closer. All tokens that were
3511
                // sharing this closer with us, except for the scope condition
3512
                // and it's opener, need to now point to the new closer.
3513
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3514
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3515
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3516
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3517
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3518
                    ) {
3519
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3520

3521
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3522
                            $line      = $this->tokens[$y]['line'];
×
3523
                            $tokenType = $this->tokens[$y]['type'];
×
3524
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3525
                            $newType   = $this->tokens[$newCloser]['type'];
×
3526
                            StatusWriter::write("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3527
                        }
3528
                    }
3529
                }
3530
            }//end if
3531

3532
            unset($this->tokens[$x]['bracket_opener']);
54✔
3533
            unset($this->tokens[$x]['bracket_closer']);
54✔
3534
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3535
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3536
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3537

3538
            // Now fix up all the tokens that think they are
3539
            // inside the CASE/DEFAULT statement when they are really outside.
3540
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3541
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3542
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3543
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3544
                        unset($this->tokens[$x]['conditions'][$num]);
×
3545

3546
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3547
                            $type     = $this->tokens[$x]['type'];
×
3548
                            $oldConds = '';
×
3549
                            foreach ($oldConditions as $condition) {
×
3550
                                $oldConds .= Tokens::tokenName($condition).',';
×
3551
                            }
3552

3553
                            $oldConds = rtrim($oldConds, ',');
×
3554

3555
                            $newConds = '';
×
3556
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3557
                                $newConds .= Tokens::tokenName($condition).',';
×
3558
                            }
3559

3560
                            $newConds = rtrim($newConds, ',');
×
3561

3562
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
3563
                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", 3);
×
3564
                        }
3565

3566
                        break;
×
3567
                    }//end if
3568
                }//end foreach
3569
            }//end for
3570
        }//end for
3571

3572
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,707✔
3573
            StatusWriter::write('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3574
        }
3575

3576
    }//end processAdditional()
569✔
3577

3578

3579
    /**
3580
     * Takes a token produced from <code>token_get_all()</code> and produces a
3581
     * more uniform token.
3582
     *
3583
     * @param string|array $token The token to convert.
3584
     *
3585
     * @return array The new token.
3586
     */
3587
    public static function standardiseToken($token)
1✔
3588
    {
3589
        if (isset($token[1]) === false) {
1✔
3590
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
1✔
3591
                return self::$resolveTokenCache[$token[0]];
1✔
3592
            }
3593
        } else {
3594
            $cacheKey = null;
1✔
3595
            if ($token[0] === T_STRING) {
1✔
3596
                $cacheKey = strtolower($token[1]);
1✔
3597
            } else if ($token[0] !== T_CURLY_OPEN) {
1✔
3598
                $cacheKey = $token[0];
1✔
3599
            }
3600

3601
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
1✔
3602
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3603
                $newToken['content'] = $token[1];
×
3604
                return $newToken;
×
3605
            }
3606
        }
3607

3608
        if (isset($token[1]) === false) {
1✔
3609
            return self::resolveSimpleToken($token[0]);
1✔
3610
        }
3611

3612
        if ($token[0] === T_STRING) {
1✔
3613
            switch ($cacheKey) {
1✔
3614
            case 'false':
1✔
3615
                $newToken['type'] = 'T_FALSE';
1✔
3616
                break;
1✔
3617
            case 'true':
1✔
3618
                $newToken['type'] = 'T_TRUE';
1✔
3619
                break;
1✔
3620
            case 'null':
1✔
3621
                $newToken['type'] = 'T_NULL';
1✔
3622
                break;
1✔
3623
            case 'self':
1✔
3624
                $newToken['type'] = 'T_SELF';
1✔
3625
                break;
1✔
3626
            case 'parent':
1✔
3627
                $newToken['type'] = 'T_PARENT';
1✔
3628
                break;
1✔
3629
            default:
3630
                $newToken['type'] = 'T_STRING';
1✔
3631
                break;
1✔
3632
            }
3633

3634
            $newToken['code'] = constant($newToken['type']);
1✔
3635

3636
            self::$resolveTokenCache[$cacheKey] = $newToken;
1✔
3637
        } else if ($token[0] === T_CURLY_OPEN) {
1✔
3638
            $newToken = [
3639
                'code' => T_OPEN_CURLY_BRACKET,
×
3640
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3641
            ];
3642
        } else {
3643
            $newToken = [
3644
                'code' => $token[0],
1✔
3645
                'type' => Tokens::tokenName($token[0]),
1✔
3646
            ];
3647

3648
            self::$resolveTokenCache[$token[0]] = $newToken;
1✔
3649
        }//end if
3650

3651
        $newToken['content'] = $token[1];
1✔
3652
        return $newToken;
1✔
3653

3654
    }//end standardiseToken()
3655

3656

3657
    /**
3658
     * Converts simple tokens into a format that conforms to complex tokens
3659
     * produced by token_get_all().
3660
     *
3661
     * Simple tokens are tokens that are not in array form when produced from
3662
     * token_get_all().
3663
     *
3664
     * @param string $token The simple token to convert.
3665
     *
3666
     * @return array The new token in array format.
3667
     */
3668
    public static function resolveSimpleToken($token)
1✔
3669
    {
3670
        $newToken = [];
1✔
3671

3672
        switch ($token) {
1✔
3673
        case '{':
1✔
3674
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1✔
3675
            break;
1✔
3676
        case '}':
1✔
3677
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1✔
3678
            break;
1✔
3679
        case '[':
1✔
3680
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1✔
3681
            break;
1✔
3682
        case ']':
1✔
3683
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1✔
3684
            break;
1✔
3685
        case '(':
1✔
3686
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
1✔
3687
            break;
1✔
3688
        case ')':
1✔
3689
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
1✔
3690
            break;
1✔
3691
        case ':':
1✔
3692
            $newToken['type'] = 'T_COLON';
1✔
3693
            break;
1✔
3694
        case '.':
1✔
3695
            $newToken['type'] = 'T_STRING_CONCAT';
1✔
3696
            break;
1✔
3697
        case ';':
1✔
3698
            $newToken['type'] = 'T_SEMICOLON';
1✔
3699
            break;
1✔
3700
        case '=':
1✔
3701
            $newToken['type'] = 'T_EQUAL';
1✔
3702
            break;
1✔
3703
        case '*':
1✔
3704
            $newToken['type'] = 'T_MULTIPLY';
1✔
3705
            break;
1✔
3706
        case '/':
1✔
3707
            $newToken['type'] = 'T_DIVIDE';
1✔
3708
            break;
1✔
3709
        case '+':
1✔
3710
            $newToken['type'] = 'T_PLUS';
1✔
3711
            break;
1✔
3712
        case '-':
1✔
3713
            $newToken['type'] = 'T_MINUS';
1✔
3714
            break;
1✔
3715
        case '%':
1✔
3716
            $newToken['type'] = 'T_MODULUS';
1✔
3717
            break;
1✔
3718
        case '^':
1✔
3719
            $newToken['type'] = 'T_BITWISE_XOR';
1✔
3720
            break;
1✔
3721
        case '&':
1✔
3722
            $newToken['type'] = 'T_BITWISE_AND';
1✔
3723
            break;
1✔
3724
        case '|':
1✔
3725
            $newToken['type'] = 'T_BITWISE_OR';
1✔
3726
            break;
1✔
3727
        case '~':
1✔
3728
            $newToken['type'] = 'T_BITWISE_NOT';
1✔
3729
            break;
1✔
3730
        case '<':
1✔
3731
            $newToken['type'] = 'T_LESS_THAN';
1✔
3732
            break;
1✔
3733
        case '>':
1✔
3734
            $newToken['type'] = 'T_GREATER_THAN';
1✔
3735
            break;
1✔
3736
        case '!':
1✔
3737
            $newToken['type'] = 'T_BOOLEAN_NOT';
1✔
3738
            break;
1✔
3739
        case ',':
1✔
3740
            $newToken['type'] = 'T_COMMA';
1✔
3741
            break;
1✔
3742
        case '@':
1✔
3743
            $newToken['type'] = 'T_ASPERAND';
1✔
3744
            break;
1✔
3745
        case '$':
1✔
3746
            $newToken['type'] = 'T_DOLLAR';
1✔
3747
            break;
1✔
3748
        case '`':
1✔
3749
            $newToken['type'] = 'T_BACKTICK';
1✔
3750
            break;
1✔
3751
        default:
3752
            $newToken['type'] = 'T_NONE';
×
3753
            break;
×
3754
        }//end switch
3755

3756
        $newToken['code']    = constant($newToken['type']);
1✔
3757
        $newToken['content'] = $token;
1✔
3758

3759
        self::$resolveTokenCache[$token] = $newToken;
1✔
3760
        return $newToken;
1✔
3761

3762
    }//end resolveSimpleToken()
3763

3764

3765
    /**
3766
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3767
     * Handle parenthesis balancing while searching for closing token
3768
     *
3769
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3770
     * @param int             $start        The starting position.
3771
     * @param string|string[] $openerTokens The opening character.
3772
     * @param string          $closerChar   The closing character.
3773
     *
3774
     * @return int|null The position of the closing token, if found. NULL otherwise.
3775
     */
3776
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3777
    {
3778
        $numTokens    = count($tokens);
51✔
3779
        $stack        = [0];
51✔
3780
        $closer       = null;
51✔
3781
        $openerTokens = (array) $openerTokens;
51✔
3782

3783
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3784
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3785
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3786
            ) {
3787
                $stack[] = $x;
51✔
3788
            } else if ($tokens[$x] === $closerChar) {
51✔
3789
                array_pop($stack);
51✔
3790
                if (empty($stack) === true) {
51✔
3791
                    $closer = $x;
51✔
3792
                    break;
51✔
3793
                }
3794
            }
3795
        }
3796

3797
        return $closer;
51✔
3798

3799
    }//end findCloser()
3800

3801

3802
    /**
3803
     * PHP 8 attributes parser for PHP < 8
3804
     * Handles single-line and multiline attributes.
3805
     *
3806
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3807
     * @param int   $stackPtr The current position in token array.
3808
     *
3809
     * @return array|null The array of parsed attribute tokens
3810
     */
3811
    private function parsePhpAttribute(array &$tokens, $stackPtr)
19✔
3812
    {
3813

3814
        $token = $tokens[$stackPtr];
19✔
3815

3816
        $commentBody = substr($token[1], 2);
19✔
3817
        $subTokens   = @token_get_all('<?php '.$commentBody);
19✔
3818

3819
        foreach ($subTokens as $i => $subToken) {
19✔
3820
            if (is_array($subToken) === true
19✔
3821
                && $subToken[0] === T_COMMENT
19✔
3822
                && strpos($subToken[1], '#[') === 0
19✔
3823
            ) {
3824
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
19✔
3825
                if ($reparsed !== null) {
19✔
3826
                    array_splice($subTokens, $i, 1, $reparsed);
19✔
3827
                } else {
3828
                    $subToken[0] = T_ATTRIBUTE;
×
3829
                }
3830
            }
3831
        }
3832

3833
        array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
19✔
3834

3835
        // Go looking for the close bracket.
3836
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3837
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
19✔
3838
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
19✔
3839
                if (is_array($token) === true) {
19✔
3840
                    $commentBody .= $token[1];
19✔
3841
                } else {
3842
                    $commentBody .= $token;
19✔
3843
                }
3844
            }
3845

3846
            $subTokens = @token_get_all('<?php '.$commentBody);
19✔
3847
            array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
19✔
3848

3849
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3850
            if ($bracketCloser !== null) {
19✔
3851
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
19✔
3852
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
19✔
3853
            }
3854
        }
3855

3856
        if ($bracketCloser === null) {
19✔
3857
            return null;
19✔
3858
        }
3859

3860
        return $subTokens;
19✔
3861

3862
    }//end parsePhpAttribute()
3863

3864

3865
    /**
3866
     * Creates a map for the attributes tokens that surround other tokens.
3867
     *
3868
     * @return void
3869
     */
3870
    private function createAttributesNestingMap()
3✔
3871
    {
3872
        $map = [];
3✔
3873
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
3874
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
3875
                && $i === $this->tokens[$i]['attribute_opener']
3✔
3876
            ) {
3877
                if (empty($map) === false) {
3✔
3878
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3879
                }
3880

3881
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
3882
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
3883
                        = $this->tokens[$i]['attribute_closer'];
3✔
3884
                }
3885
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
3886
                && $i === $this->tokens[$i]['attribute_closer']
3✔
3887
            ) {
3888
                array_pop($map);
3✔
3889
                if (empty($map) === false) {
3✔
3890
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3891
                }
3892
            } else {
3893
                if (empty($map) === false) {
3✔
3894
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3895
                }
3896
            }//end if
3897
        }//end for
3898

3899
    }//end createAttributesNestingMap()
1✔
3900

3901

3902
}//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

© 2026 Coveralls, Inc