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

PHPCSStandards / PHP_CodeSniffer / 17174734458

23 Aug 2025 11:06AM UTC coverage: 76.88% (-2.1%) from 78.934%
17174734458

push

github

jrfnl
TEMP/TESTING PHPUnit 6331

19187 of 24957 relevant lines covered (76.88%)

60.25 hits per line

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

68.83
/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
     * Contexts in which keywords should always be tokenized as T_STRING.
21
     *
22
     * @var array<int|string, true>
23
     */
24
    protected const T_STRING_CONTEXTS = [
25
        T_OBJECT_OPERATOR          => true,
26
        T_NULLSAFE_OBJECT_OPERATOR => true,
27
        T_FUNCTION                 => true,
28
        T_CLASS                    => true,
29
        T_INTERFACE                => true,
30
        T_TRAIT                    => true,
31
        T_ENUM                     => true,
32
        T_ENUM_CASE                => true,
33
        T_EXTENDS                  => true,
34
        T_IMPLEMENTS               => true,
35
        T_ATTRIBUTE                => true,
36
        T_NEW                      => true,
37
        T_CONST                    => true,
38
        T_NS_SEPARATOR             => true,
39
        T_USE                      => true,
40
        T_NAMESPACE                => true,
41
        T_PAAMAYIM_NEKUDOTAYIM     => true,
42
        T_GOTO                     => true,
43
    ];
44

45
    /**
46
     * Regular expression to check if a given identifier name is valid for use in PHP.
47
     *
48
     * @var string
49
     */
50
    private const PHP_LABEL_REGEX = '`^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$`';
51

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

321
    /**
322
     * A list of tokens that end the scope.
323
     *
324
     * This array is just a unique collection of the end tokens
325
     * from the scopeOpeners array. The data is duplicated here to
326
     * save time during parsing of the file.
327
     *
328
     * @var array<int|string, int|string>
329
     */
330
    public $endScopeTokens = [
331
        T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
332
        T_ENDIF               => T_ENDIF,
333
        T_ENDFOR              => T_ENDFOR,
334
        T_ENDFOREACH          => T_ENDFOREACH,
335
        T_ENDWHILE            => T_ENDWHILE,
336
        T_ENDSWITCH           => T_ENDSWITCH,
337
        T_ENDDECLARE          => T_ENDDECLARE,
338
        T_BREAK               => T_BREAK,
339
        T_END_HEREDOC         => T_END_HEREDOC,
340
        T_END_NOWDOC          => T_END_NOWDOC,
341
    ];
342

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

510
    /**
511
     * Contexts in which keywords should always be tokenized as T_STRING.
512
     *
513
     * @var array
514
     *
515
     * @deprecated 4.0.0 Use the PHP::T_STRING_CONTEXTS constant instead.
516
     */
517
    protected $tstringContexts = self::T_STRING_CONTEXTS;
518

519
    /**
520
     * A cache of different token types, resolved into arrays.
521
     *
522
     * @var array
523
     * @see standardiseToken()
524
     */
525
    private static $resolveTokenCache = [];
526

527

528
    /**
529
     * Creates an array of tokens when given some PHP code.
530
     *
531
     * Starts by using token_get_all() but does a lot of extra processing
532
     * to insert information about the context of the token.
533
     *
534
     * @param string $string The string to tokenize.
535
     *
536
     * @return array
537
     */
538
    protected function tokenize($string)
2,242✔
539
    {
540
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,242✔
541
            StatusWriter::write('*** START PHP TOKENIZING ***', 1);
×
542
            $isWin = false;
×
543
            if (PHP_OS_FAMILY === 'Windows') {
×
544
                $isWin = true;
×
545
            }
546
        }
547

548
        $tokens      = @token_get_all($string);
2,242✔
549
        $finalTokens = [];
2,242✔
550

551
        $newStackPtr       = 0;
2,242✔
552
        $numTokens         = count($tokens);
2,242✔
553
        $lastNotEmptyToken = 0;
2,242✔
554

555
        $insideInlineIf         = [];
2,242✔
556
        $insideUseGroup         = false;
2,242✔
557
        $insideConstDeclaration = false;
2,242✔
558

559
        $commentTokenizer = new Comment();
2,242✔
560

561
        for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
2,242✔
562
            // Special case for tokens we have needed to blank out.
563
            if ($tokens[$stackPtr] === null) {
2,242✔
564
                continue;
1,412✔
565
            }
566

567
            $token        = (array) $tokens[$stackPtr];
2,242✔
568
            $tokenIsArray = isset($token[1]);
2,242✔
569

570
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,242✔
571
                if ($tokenIsArray === true) {
×
572
                    $type    = Tokens::tokenName($token[0]);
×
573
                    $content = Common::prepareForOutput($token[1]);
×
574
                } else {
575
                    $newToken = self::resolveSimpleToken($token[0]);
×
576
                    $type     = $newToken['type'];
×
577
                    $content  = Common::prepareForOutput($token[0]);
×
578
                }
579

580
                $statusMessage = 'Process token ';
×
581
                if ($tokenIsArray === true) {
×
582
                    $statusMessage .= "[$stackPtr]";
×
583
                } else {
584
                    $statusMessage .= " $stackPtr ";
×
585
                }
586

587
                $statusMessage .= ": $type => $content";
×
588
                StatusWriter::write($statusMessage, 1, 0);
×
589
            }//end if
590

591
            if ($newStackPtr > 0
2,242✔
592
                && isset(Tokens::EMPTY_TOKENS[$finalTokens[($newStackPtr - 1)]['code']]) === false
2,242✔
593
            ) {
594
                $lastNotEmptyToken = ($newStackPtr - 1);
2,242✔
595
            }
596

597
            /*
598
                If we are using \r\n newline characters, the \r and \n are sometimes
599
                split over two tokens. This normally occurs after comments. We need
600
                to merge these two characters together so that our line endings are
601
                consistent for all lines.
602
            */
603

604
            if ($tokenIsArray === true && substr($token[1], -1) === "\r") {
2,242✔
605
                if (isset($tokens[($stackPtr + 1)]) === true
×
606
                    && is_array($tokens[($stackPtr + 1)]) === true
×
607
                    && $tokens[($stackPtr + 1)][1][0] === "\n"
×
608
                ) {
609
                    $token[1] .= "\n";
×
610
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
611
                        if ($isWin === true) {
×
612
                            StatusWriter::write('\n', 0, 0);
×
613
                        } else {
614
                            StatusWriter::write("\033[30;1m\\n\033[0m", 0, 0);
×
615
                        }
616
                    }
617

618
                    if ($tokens[($stackPtr + 1)][1] === "\n") {
×
619
                        // This token's content has been merged into the previous,
620
                        // so we can skip it.
621
                        $tokens[($stackPtr + 1)] = '';
×
622
                    } else {
623
                        $tokens[($stackPtr + 1)][1] = substr($tokens[($stackPtr + 1)][1], 1);
×
624
                    }
625
                }
626
            }//end if
627

628
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,242✔
629
                StatusWriter::writeNewline();
×
630
            }
631

632
            /*
633
                Tokenize context sensitive keyword as string when it should be string.
634
            */
635

636
            if ($tokenIsArray === true
2,242✔
637
                && isset(Tokens::CONTEXT_SENSITIVE_KEYWORDS[$token[0]]) === true
2,242✔
638
                && (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,242✔
639
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
2,242✔
640
                || $insideConstDeclaration === true)
2,242✔
641
            ) {
642
                if (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
1,732✔
643
                    $preserveKeyword = false;
1,732✔
644

645
                    // `new class`, and `new static` should be preserved.
646
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
1,732✔
647
                        && ($token[0] === T_CLASS
1,732✔
648
                        || $token[0] === T_STATIC)
1,732✔
649
                    ) {
650
                        $preserveKeyword = true;
1,018✔
651
                    }
652

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

665
                        if (is_array($tokens[$i]) === true && $tokens[$i][0] === T_CLASS) {
344✔
666
                            $preserveKeyword = true;
344✔
667
                        }
668
                    }
669

670
                    // `new class extends` `new class implements` should be preserved
671
                    if (($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS)
1,732✔
672
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CLASS
1,732✔
673
                    ) {
674
                        $preserveKeyword = true;
344✔
675
                    }
676

677
                    // `namespace\` should be preserved
678
                    if ($token[0] === T_NAMESPACE) {
1,732✔
679
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
484✔
680
                            if (is_array($tokens[$i]) === false) {
484✔
681
                                break;
484✔
682
                            }
683

684
                            if (isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true) {
344✔
685
                                continue;
344✔
686
                            }
687

688
                            if ($tokens[$i][0] === T_NS_SEPARATOR) {
×
689
                                $preserveKeyword = true;
×
690
                            }
691

692
                            break;
×
693
                        }
694
                    }
695
                }//end if
696

697
                // Types in typed constants should not be touched, but the constant name should be.
698
                if ((isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
1,732✔
699
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
1,732✔
700
                    || $insideConstDeclaration === true
1,732✔
701
                ) {
702
                    $preserveKeyword = true;
1,550✔
703

704
                    // Find the next non-empty token.
705
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,550✔
706
                        if (is_array($tokens[$i]) === true
1,550✔
707
                            && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
1,550✔
708
                        ) {
709
                            continue;
1,124✔
710
                        }
711

712
                        break;
1,550✔
713
                    }
714

715
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
1,550✔
716
                        $preserveKeyword        = false;
578✔
717
                        $insideConstDeclaration = false;
578✔
718
                    }
719
                }//end if
720

721
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
1,732✔
722
                    $preserveKeyword = true;
380✔
723

724
                    for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
380✔
725
                        if (isset(Tokens::EMPTY_TOKENS[$finalTokens[$i]['code']]) === true) {
380✔
726
                            continue;
380✔
727
                        }
728

729
                        if ($finalTokens[$i]['code'] === T_FUNCTION) {
380✔
730
                            $preserveKeyword = false;
380✔
731
                        }
732

733
                        break;
380✔
734
                    }
735
                }
736

737
                if ($preserveKeyword === false) {
1,732✔
738
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
860✔
739
                        $type = Tokens::tokenName($token[0]);
×
740
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
741
                    }
742

743
                    $finalTokens[$newStackPtr] = [
860✔
744
                        'code'    => T_STRING,
860✔
745
                        'type'    => 'T_STRING',
860✔
746
                        'content' => $token[1],
860✔
747
                    ];
860✔
748

749
                    $newStackPtr++;
860✔
750
                    continue;
860✔
751
                }
752
            }//end if
753

754
            /*
755
                Mark the start of a constant declaration to allow for handling keyword to T_STRING
756
                convertion for constant names using reserved keywords.
757
            */
758

759
            if ($tokenIsArray === true && $token[0] === T_CONST) {
2,242✔
760
                $insideConstDeclaration = true;
1,636✔
761
            }
762

763
            /*
764
                Close an open "inside constant declaration" marker when no keyword conversion was needed.
765
            */
766

767
            if ($insideConstDeclaration === true
2,242✔
768
                && $tokenIsArray === false
2,242✔
769
                && ($token[0] === '=' || $token[0] === ';')
2,242✔
770
            ) {
771
                $insideConstDeclaration = false;
526✔
772
            }
773

774
            /*
775
                Special case for `static` used as a function name, i.e. `static()`.
776

777
                Note: this may incorrectly change the static keyword directly before a DNF property type.
778
                If so, this will be caught and corrected for in the additional processing.
779
            */
780

781
            if ($tokenIsArray === true
2,242✔
782
                && $token[0] === T_STATIC
2,242✔
783
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
2,242✔
784
            ) {
785
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,372✔
786
                    if (is_array($tokens[$i]) === true
1,372✔
787
                        && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
1,372✔
788
                    ) {
789
                        continue;
946✔
790
                    }
791

792
                    if ($tokens[$i][0] === '(') {
1,372✔
793
                        $finalTokens[$newStackPtr] = [
344✔
794
                            'code'    => T_STRING,
344✔
795
                            'type'    => 'T_STRING',
344✔
796
                            'content' => $token[1],
344✔
797
                        ];
344✔
798

799
                        $newStackPtr++;
344✔
800
                        continue 2;
344✔
801
                    }
802

803
                    break;
1,372✔
804
                }
805
            }//end if
806

807
            /*
808
                Prior to PHP 7.4, PHP didn't support stand-alone PHP open tags at the end of a file
809
                (without a new line), so we need to make sure that the tokenization in PHPCS is consistent
810
                cross-version PHP by retokenizing to T_OPEN_TAG.
811
            */
812

813
            if (PHP_VERSION_ID < 70400
2,242✔
814
                && $tokenIsArray === true
2,242✔
815
                // PHP < 7.4 with short open tags off.
816
                && (($stackPtr === ($numTokens - 1)
2,242✔
817
                && $token[0] === T_INLINE_HTML
2,242✔
818
                && stripos($token[1], '<?php') === 0)
2,242✔
819
                // PHP < 7.4 with short open tags on.
2,242✔
820
                || ($stackPtr === ($numTokens - 2)
2,242✔
821
                && $token[0] === T_OPEN_TAG
2,242✔
822
                && $token[1] === '<?'
2,242✔
823
                && is_array($tokens[($stackPtr + 1)]) === true
2,242✔
824
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,242✔
825
                && strtolower($tokens[($stackPtr + 1)][1]) === 'php'))
2,242✔
826
            ) {
827
                if ($token[0] === T_INLINE_HTML) {
×
828
                    $finalTokens[$newStackPtr] = [
×
829
                        'code'    => T_OPEN_TAG,
×
830
                        'type'    => 'T_OPEN_TAG',
×
831
                        'content' => $token[1],
×
832
                    ];
×
833
                } else {
834
                    $finalTokens[$newStackPtr] = [
×
835
                        'code'    => T_OPEN_TAG,
×
836
                        'type'    => 'T_OPEN_TAG',
×
837
                        'content' => $token[1].$tokens[($stackPtr + 1)][1],
×
838
                    ];
×
839

840
                    $stackPtr++;
×
841
                }
842

843
                $newStackPtr++;
×
844
                continue;
×
845
            }//end if
846

847
            /*
848
                Split whitespace off from long PHP open tag tokens and potentially join the whitespace
849
                with a subsequent whitespace token.
850
            */
851

852
            if ($tokenIsArray === true
2,242✔
853
                && $token[0] === T_OPEN_TAG
2,242✔
854
                && stripos($token[1], '<?php') === 0
2,242✔
855
            ) {
856
                $openTagAndWhiteSpace = str_split($token[1], 5);
2,242✔
857

858
                $finalTokens[$newStackPtr] = [
2,242✔
859
                    'code'    => T_OPEN_TAG,
2,242✔
860
                    'type'    => 'T_OPEN_TAG',
2,242✔
861
                    'content' => $openTagAndWhiteSpace[0],
2,242✔
862
                ];
2,242✔
863
                $newStackPtr++;
2,242✔
864

865
                if (isset($openTagAndWhiteSpace[1]) === true) {
2,242✔
866
                    // The original open tag token included whitespace.
867
                    // Check whether a new whitespace token needs to be inserted or if the
868
                    // whitespace needs to be joined with a pre-existing whitespace
869
                    // token on the same line as the open tag.
870
                    if (isset($tokens[($stackPtr + 1)]) === true
2,242✔
871
                        && $openTagAndWhiteSpace[1] === ' '
2,242✔
872
                        && is_array($tokens[($stackPtr + 1)]) === true
2,242✔
873
                        && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
2,242✔
874
                    ) {
875
                        // Adjusting the original token stack as the "new line may be split over two tokens"
876
                        // check should still be run on this token.
877
                        $tokens[($stackPtr + 1)][1] = $openTagAndWhiteSpace[1].$tokens[($stackPtr + 1)][1];
44✔
878

879
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
44✔
880
                            StatusWriter::write("* removed whitespace from T_OPEN_TAG token $stackPtr and merged it with the next token T_WHITESPACE", 2);
×
881
                        }
882
                    } else {
883
                        $finalTokens[$newStackPtr] = [
2,242✔
884
                            'code'    => T_WHITESPACE,
2,242✔
885
                            'type'    => 'T_WHITESPACE',
2,242✔
886
                            'content' => $openTagAndWhiteSpace[1],
2,242✔
887
                        ];
2,242✔
888

889
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,242✔
890
                            StatusWriter::write("* T_OPEN_TAG token $stackPtr split into T_OPEN_TAG (without whitespace) and new T_WHITESPACE token", 2);
×
891
                        }
892

893
                        $newStackPtr++;
2,242✔
894
                    }//end if
895
                }//end if
896

897
                continue;
2,242✔
898
            }//end if
899

900
            /*
901
                Parse doc blocks into something that can be easily iterated over.
902
            */
903

904
            if ($tokenIsArray === true
2,242✔
905
                && ($token[0] === T_DOC_COMMENT
2,242✔
906
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
2,242✔
907
            ) {
908
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
62✔
909
                $finalTokens  += $commentTokens;
62✔
910
                $newStackPtr  += count($commentTokens);
62✔
911
                continue;
62✔
912
            }
913

914
            /*
915
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
916
            */
917

918
            if (PHP_VERSION_ID >= 80000
2,242✔
919
                && $tokenIsArray === true
2,242✔
920
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
2,242✔
921
                && isset($tokens[($stackPtr + 1)]) === true
2,242✔
922
                && is_array($tokens[($stackPtr + 1)]) === true
2,242✔
923
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
2,242✔
924
            ) {
925
                $nextToken = $tokens[($stackPtr + 1)];
1,728✔
926

927
                // If the next token is a single new line, merge it into the comment token
928
                // and set to it up to be skipped.
929
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
1,728✔
930
                    $token[1] .= $nextToken[1];
1,412✔
931
                    $tokens[($stackPtr + 1)] = null;
1,412✔
932

933
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,412✔
934
                        StatusWriter::write("* merged newline after comment into comment token $stackPtr", 2);
×
935
                    }
936
                } else {
937
                    // This may be a whitespace token consisting of multiple new lines.
938
                    if (strpos($nextToken[1], "\r\n") === 0) {
1,388✔
939
                        $token[1] .= "\r\n";
30✔
940
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
30✔
941
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
1,358✔
942
                        $token[1] .= "\n\r";
×
943
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
944
                    } else if (strpos($nextToken[1], "\n") === 0) {
1,358✔
945
                        $token[1] .= "\n";
1,358✔
946
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
1,358✔
947
                    }
948

949
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,388✔
950
                        StatusWriter::write("* stripped first newline after comment and added it to comment token $stackPtr", 2);
×
951
                    }
952
                }//end if
953
            }//end if
954

955
            /*
956
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
957
                T_LNUMBER and T_STRING token values into a single token value, and
958
                then ignore the T_STRING token.
959
            */
960

961
            if (PHP_VERSION_ID < 80100
2,242✔
962
                && $tokenIsArray === true && $token[1] === '0'
2,242✔
963
                && (isset($tokens[($stackPtr + 1)]) === true
2,242✔
964
                && is_array($tokens[($stackPtr + 1)]) === true
2,242✔
965
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,242✔
966
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,242✔
967
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,242✔
968
                && $tokens[($stackPtr + 1)][1][1] !== '_')
2,242✔
969
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
2,242✔
970
            ) {
971
                $finalTokens[$newStackPtr] = [
×
972
                    'code'    => T_LNUMBER,
×
973
                    'type'    => 'T_LNUMBER',
×
974
                    'content' => $token[1] .= $matches[1],
×
975
                ];
×
976
                $newStackPtr++;
×
977

978
                if (isset($matches[2]) === true && $matches[2] !== '') {
×
979
                    $type = 'T_LNUMBER';
×
980
                    if ($matches[2][0] === '_') {
×
981
                        $type = 'T_STRING';
×
982
                    }
983

984
                    $finalTokens[$newStackPtr] = [
×
985
                        'code'    => constant($type),
×
986
                        'type'    => $type,
×
987
                        'content' => $matches[2],
×
988
                    ];
×
989
                    $newStackPtr++;
×
990
                }
991

992
                $stackPtr++;
×
993
                continue;
×
994
            }//end if
995

996
            /*
997
                PHP 8.1 introduced two dedicated tokens for the & character.
998
                Retokenizing both of these to T_BITWISE_AND, which is the
999
                token PHPCS already tokenized them as.
1000
            */
1001

1002
            if ($tokenIsArray === true
2,242✔
1003
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
2,242✔
1004
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
2,242✔
1005
            ) {
1006
                $finalTokens[$newStackPtr] = [
864✔
1007
                    'code'    => T_BITWISE_AND,
864✔
1008
                    'type'    => 'T_BITWISE_AND',
864✔
1009
                    'content' => $token[1],
864✔
1010
                ];
864✔
1011
                $newStackPtr++;
864✔
1012
                continue;
864✔
1013
            }
1014

1015
            /*
1016
                If this is a double quoted string, PHP will tokenize the whole
1017
                thing which causes problems with the scope map when braces are
1018
                within the string. So we need to merge the tokens together to
1019
                provide a single string.
1020
            */
1021

1022
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
2,242✔
1023
                // Binary casts need a special token.
1024
                if ($token[0] === 'b"') {
100✔
1025
                    $finalTokens[$newStackPtr] = [
×
1026
                        'code'    => T_BINARY_CAST,
×
1027
                        'type'    => 'T_BINARY_CAST',
×
1028
                        'content' => 'b',
×
1029
                    ];
×
1030
                    $newStackPtr++;
×
1031
                }
1032

1033
                $tokenContent = '"';
100✔
1034
                $nestedVars   = [];
100✔
1035
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
100✔
1036
                    $subToken        = (array) $tokens[$i];
100✔
1037
                    $subTokenIsArray = isset($subToken[1]);
100✔
1038

1039
                    if ($subTokenIsArray === true) {
100✔
1040
                        $tokenContent .= $subToken[1];
100✔
1041
                        if (($subToken[1] === '{'
100✔
1042
                            || $subToken[1] === '${')
100✔
1043
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
100✔
1044
                        ) {
1045
                            $nestedVars[] = $i;
40✔
1046
                        }
1047
                    } else {
1048
                        $tokenContent .= $subToken[0];
100✔
1049
                        if ($subToken[0] === '}') {
100✔
1050
                            array_pop($nestedVars);
40✔
1051
                        }
1052
                    }
1053

1054
                    if ($subTokenIsArray === false
100✔
1055
                        && $subToken[0] === '"'
100✔
1056
                        && empty($nestedVars) === true
100✔
1057
                    ) {
1058
                        // We found the other end of the double quoted string.
1059
                        break;
100✔
1060
                    }
1061
                }//end for
1062

1063
                $stackPtr = $i;
100✔
1064

1065
                // Convert each line within the double quoted string to a
1066
                // new token, so it conforms with other multiple line tokens.
1067
                $tokenLines = explode($this->eolChar, $tokenContent);
100✔
1068
                $numLines   = count($tokenLines);
100✔
1069
                $newToken   = [];
100✔
1070

1071
                for ($j = 0; $j < $numLines; $j++) {
100✔
1072
                    $newToken['content'] = $tokenLines[$j];
100✔
1073
                    if ($j === ($numLines - 1)) {
100✔
1074
                        if ($tokenLines[$j] === '') {
100✔
1075
                            break;
40✔
1076
                        }
1077
                    } else {
1078
                        $newToken['content'] .= $this->eolChar;
40✔
1079
                    }
1080

1081
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
100✔
1082
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
100✔
1083
                    $finalTokens[$newStackPtr] = $newToken;
100✔
1084
                    $newStackPtr++;
100✔
1085
                }
1086

1087
                // Continue, as we're done with this token.
1088
                continue;
100✔
1089
            }//end if
1090

1091
            /*
1092
                Detect binary casting and assign the casts their own token.
1093
            */
1094

1095
            if ($tokenIsArray === true
2,242✔
1096
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
2,242✔
1097
                && (substr($token[1], 0, 2) === 'b"'
2,242✔
1098
                || substr($token[1], 0, 2) === "b'")
2,242✔
1099
            ) {
1100
                $finalTokens[$newStackPtr] = [
×
1101
                    'code'    => T_BINARY_CAST,
×
1102
                    'type'    => 'T_BINARY_CAST',
×
1103
                    'content' => 'b',
×
1104
                ];
×
1105
                $newStackPtr++;
×
1106
                $token[1] = substr($token[1], 1);
×
1107
            }
1108

1109
            if ($tokenIsArray === true
2,242✔
1110
                && $token[0] === T_STRING_CAST
2,242✔
1111
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
2,242✔
1112
            ) {
1113
                $finalTokens[$newStackPtr] = [
×
1114
                    'code'    => T_BINARY_CAST,
×
1115
                    'type'    => 'T_BINARY_CAST',
×
1116
                    'content' => $token[1],
×
1117
                ];
×
1118
                $newStackPtr++;
×
1119
                continue;
×
1120
            }
1121

1122
            /*
1123
                If this is a heredoc, PHP will tokenize the whole
1124
                thing which causes problems when heredocs don't
1125
                contain real PHP code, which is almost never.
1126
                We want to leave the start and end heredoc tokens
1127
                alone though.
1128
            */
1129

1130
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
2,242✔
1131
                // Add the start heredoc token to the final array.
1132
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
90✔
1133

1134
                // Check if this is actually a nowdoc and use a different token
1135
                // to help the sniffs.
1136
                $nowdoc = false;
90✔
1137
                if (strpos($token[1], "'") !== false) {
90✔
1138
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
12✔
1139
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
12✔
1140
                    $nowdoc = true;
12✔
1141
                }
1142

1143
                $tokenContent = '';
90✔
1144
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
90✔
1145
                    $subTokenIsArray = is_array($tokens[$i]);
90✔
1146
                    if ($subTokenIsArray === true
90✔
1147
                        && $tokens[$i][0] === T_END_HEREDOC
90✔
1148
                    ) {
1149
                        // We found the other end of the heredoc.
1150
                        break;
88✔
1151
                    }
1152

1153
                    if ($subTokenIsArray === true) {
90✔
1154
                        $tokenContent .= $tokens[$i][1];
90✔
1155
                    } else {
1156
                        $tokenContent .= $tokens[$i];
76✔
1157
                    }
1158
                }
1159

1160
                if ($i === $numTokens) {
90✔
1161
                    // We got to the end of the file and never
1162
                    // found the closing token, so this probably wasn't
1163
                    // a heredoc.
1164
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
2✔
1165
                        $type = $finalTokens[$newStackPtr]['type'];
×
1166
                        StatusWriter::write('* failed to find the end of the here/nowdoc', 2);
×
1167
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1168
                    }
1169

1170
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
2✔
1171
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
2✔
1172
                    $newStackPtr++;
2✔
1173
                    continue;
2✔
1174
                }
1175

1176
                $stackPtr = $i;
88✔
1177
                $newStackPtr++;
88✔
1178

1179
                // Convert each line within the heredoc to a
1180
                // new token, so it conforms with other multiple line tokens.
1181
                $tokenLines = explode($this->eolChar, $tokenContent);
88✔
1182
                $numLines   = count($tokenLines);
88✔
1183
                $newToken   = [];
88✔
1184

1185
                for ($j = 0; $j < $numLines; $j++) {
88✔
1186
                    $newToken['content'] = $tokenLines[$j];
88✔
1187
                    if ($j === ($numLines - 1)) {
88✔
1188
                        if ($tokenLines[$j] === '') {
88✔
1189
                            break;
88✔
1190
                        }
1191
                    } else {
1192
                        $newToken['content'] .= $this->eolChar;
88✔
1193
                    }
1194

1195
                    if ($nowdoc === true) {
88✔
1196
                        $newToken['code'] = T_NOWDOC;
12✔
1197
                        $newToken['type'] = 'T_NOWDOC';
12✔
1198
                    } else {
1199
                        $newToken['code'] = T_HEREDOC;
88✔
1200
                        $newToken['type'] = 'T_HEREDOC';
88✔
1201
                    }
1202

1203
                    $finalTokens[$newStackPtr] = $newToken;
88✔
1204
                    $newStackPtr++;
88✔
1205
                }//end for
1206

1207
                // Add the end heredoc token to the final array.
1208
                $finalTokens[$newStackPtr] = self::standardiseToken($tokens[$stackPtr]);
88✔
1209

1210
                if ($nowdoc === true) {
88✔
1211
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
12✔
1212
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
12✔
1213
                }
1214

1215
                $newStackPtr++;
88✔
1216

1217
                // Continue, as we're done with this token.
1218
                continue;
88✔
1219
            }//end if
1220

1221
            /*
1222
                Enum keyword for PHP < 8.1
1223
            */
1224

1225
            if ($tokenIsArray === true
2,242✔
1226
                && $token[0] === T_STRING
2,242✔
1227
                && strtolower($token[1]) === 'enum'
2,242✔
1228
            ) {
1229
                // Get the next non-empty token.
1230
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
996✔
1231
                    if (is_array($tokens[$i]) === false
996✔
1232
                        || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
996✔
1233
                    ) {
1234
                        break;
996✔
1235
                    }
1236
                }
1237

1238
                if (isset($tokens[$i]) === true
996✔
1239
                    && is_array($tokens[$i]) === true
996✔
1240
                    && $tokens[$i][0] === T_STRING
996✔
1241
                ) {
1242
                    // Modify $tokens directly so we can use it later when converting enum "case".
1243
                    $tokens[$stackPtr][0] = T_ENUM;
×
1244

1245
                    $newToken            = [];
×
1246
                    $newToken['code']    = T_ENUM;
×
1247
                    $newToken['type']    = 'T_ENUM';
×
1248
                    $newToken['content'] = $token[1];
×
1249
                    $finalTokens[$newStackPtr] = $newToken;
×
1250

1251
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1252
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_ENUM", 2);
×
1253
                    }
1254

1255
                    $newStackPtr++;
×
1256
                    continue;
×
1257
                }
1258
            }//end if
1259

1260
            /*
1261
                Convert enum "case" to T_ENUM_CASE
1262
            */
1263

1264
            if ($tokenIsArray === true
2,242✔
1265
                && $token[0] === T_CASE
2,242✔
1266
                && isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,242✔
1267
            ) {
1268
                $isEnumCase = false;
1,070✔
1269
                $scope      = 1;
1,070✔
1270

1271
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,070✔
1272
                    if ($tokens[$i] === '}') {
1,070✔
1273
                        $scope++;
1,010✔
1274
                        continue;
1,010✔
1275
                    }
1276

1277
                    if ($tokens[$i] === '{') {
1,070✔
1278
                        $scope--;
1,070✔
1279
                        continue;
1,070✔
1280
                    }
1281

1282
                    if (is_array($tokens[$i]) === false) {
1,070✔
1283
                        continue;
1,070✔
1284
                    }
1285

1286
                    if ($scope !== 0) {
1,070✔
1287
                        continue;
1,070✔
1288
                    }
1289

1290
                    if ($tokens[$i][0] === T_SWITCH) {
726✔
1291
                        break;
682✔
1292
                    }
1293

1294
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
726✔
1295
                        $isEnumCase = true;
86✔
1296
                        break;
86✔
1297
                    }
1298
                }//end for
1299

1300
                if ($isEnumCase === true) {
1,070✔
1301
                    // Modify $tokens directly so we can use it as optimisation for other enum "case".
1302
                    $tokens[$stackPtr][0] = T_ENUM_CASE;
86✔
1303

1304
                    $newToken            = [];
86✔
1305
                    $newToken['code']    = T_ENUM_CASE;
86✔
1306
                    $newToken['type']    = 'T_ENUM_CASE';
86✔
1307
                    $newToken['content'] = $token[1];
86✔
1308
                    $finalTokens[$newStackPtr] = $newToken;
86✔
1309

1310
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
86✔
1311
                        StatusWriter::write("* token $stackPtr changed from T_CASE to T_ENUM_CASE", 2);
×
1312
                    }
1313

1314
                    $newStackPtr++;
86✔
1315
                    continue;
86✔
1316
                }
1317
            }//end if
1318

1319
            /*
1320
                Asymmetric visibility for PHP < 8.4
1321
            */
1322

1323
            if ($tokenIsArray === true
2,242✔
1324
                && in_array($token[0], [T_PUBLIC, T_PROTECTED, T_PRIVATE], true) === true
2,242✔
1325
                && ($stackPtr + 3) < $numTokens
2,242✔
1326
                && $tokens[($stackPtr + 1)] === '('
2,242✔
1327
                && is_array($tokens[($stackPtr + 2)]) === true
2,242✔
1328
                && $tokens[($stackPtr + 2)][0] === T_STRING
2,242✔
1329
                && strtolower($tokens[($stackPtr + 2)][1]) === 'set'
2,242✔
1330
                && $tokens[($stackPtr + 3)] === ')'
2,242✔
1331
            ) {
1332
                $newToken = [];
×
1333
                if ($token[0] === T_PUBLIC) {
×
1334
                    $oldCode          = 'T_PUBLIC';
×
1335
                    $newToken['code'] = T_PUBLIC_SET;
×
1336
                    $newToken['type'] = 'T_PUBLIC_SET';
×
1337
                } else if ($token[0] === T_PROTECTED) {
×
1338
                    $oldCode          = 'T_PROTECTED';
×
1339
                    $newToken['code'] = T_PROTECTED_SET;
×
1340
                    $newToken['type'] = 'T_PROTECTED_SET';
×
1341
                } else {
1342
                    $oldCode          = 'T_PRIVATE';
×
1343
                    $newToken['code'] = T_PRIVATE_SET;
×
1344
                    $newToken['type'] = 'T_PRIVATE_SET';
×
1345
                }
1346

1347
                $newToken['content']       = $token[1].'('.$tokens[($stackPtr + 2)][1].')';
×
1348
                $finalTokens[$newStackPtr] = $newToken;
×
1349
                $newStackPtr++;
×
1350

1351
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1352
                    $newCode = $newToken['type'];
×
1353
                    StatusWriter::write("* tokens from $stackPtr changed from $oldCode to $newCode", 2);
×
1354
                }
1355

1356
                // We processed an extra 3 tokens, for `(`, `set`, and `)`.
1357
                $stackPtr += 3;
×
1358

1359
                continue;
×
1360
            }//end if
1361

1362
            /*
1363
                Before PHP 8.0, namespaced names were not tokenized as a single token.
1364

1365
                Note: reserved keywords are allowed within the "single token" names, so
1366
                no check is done on the token type following a namespace separator _on purpose_.
1367
                As long as it is not an empty token and the token contents complies with the
1368
                "name" requirements in PHP, we'll accept it.
1369
            */
1370

1371
            if (PHP_VERSION_ID < 80000
2,242✔
1372
                && $tokenIsArray === true
2,242✔
1373
                && ($token[0] === T_STRING
2,242✔
1374
                || $token[0] === T_NAMESPACE
2,242✔
1375
                || ($token[0] === T_NS_SEPARATOR
2,242✔
1376
                && isset($tokens[($stackPtr + 1)]) === true
2,242✔
1377
                && is_array($tokens[($stackPtr + 1)]) === true
2,242✔
1378
                && isset(Tokens::EMPTY_TOKENS[$tokens[($stackPtr + 1)][0]]) === false
2,242✔
1379
                && preg_match(self::PHP_LABEL_REGEX, $tokens[($stackPtr + 1)][1]) === 1))
2,242✔
1380
            ) {
1381
                $nameStart = $stackPtr;
×
1382
                $i         = $stackPtr;
×
1383
                $newToken  = [];
×
1384
                $newToken['content'] = $token[1];
×
1385

1386
                switch ($token[0]) {
×
1387
                case T_STRING:
×
1388
                    $newToken['code'] = T_NAME_QUALIFIED;
×
1389
                    $newToken['type'] = 'T_NAME_QUALIFIED';
×
1390
                    break;
×
1391
                case T_NAMESPACE:
×
1392
                    $newToken['code'] = T_NAME_RELATIVE;
×
1393
                    $newToken['type'] = 'T_NAME_RELATIVE';
×
1394
                    break;
×
1395
                case T_NS_SEPARATOR:
×
1396
                    $newToken['code'] = T_NAME_FULLY_QUALIFIED;
×
1397
                    $newToken['type'] = 'T_NAME_FULLY_QUALIFIED';
×
1398

1399
                    if (is_array($tokens[($i - 1)]) === true
×
1400
                        && isset(Tokens::EMPTY_TOKENS[$tokens[($i - 1)][0]]) === false
×
1401
                        && preg_match(self::PHP_LABEL_REGEX, $tokens[($i - 1)][1]) === 1
×
1402
                    ) {
1403
                        // The namespaced name starts with a reserved keyword. Move one token back.
1404
                        $newToken['code']    = T_NAME_QUALIFIED;
×
1405
                        $newToken['type']    = 'T_NAME_QUALIFIED';
×
1406
                        $newToken['content'] = $tokens[($i - 1)][1];
×
1407
                        --$nameStart;
×
1408
                        --$i;
×
1409
                        break;
×
1410
                    }
1411

1412
                    ++$i;
×
1413
                    $newToken['content'] .= $tokens[$i][1];
×
1414
                    break;
×
1415
                }//end switch
1416

1417
                while (isset($tokens[($i + 1)], $tokens[($i + 2)]) === true
×
1418
                    && is_array($tokens[($i + 1)]) === true && $tokens[($i + 1)][0] === T_NS_SEPARATOR
×
1419
                    && is_array($tokens[($i + 2)]) === true
×
1420
                    && isset(Tokens::EMPTY_TOKENS[$tokens[($i + 2)][0]]) === false
×
1421
                    && preg_match(self::PHP_LABEL_REGEX, $tokens[($i + 2)][1]) === 1
×
1422
                ) {
1423
                    $newToken['content'] .= $tokens[($i + 1)][1].$tokens[($i + 2)][1];
×
1424
                    $i = ($i + 2);
×
1425
                }
1426

1427
                if ($i !== $nameStart) {
×
1428
                    if ($nameStart !== $stackPtr) {
×
1429
                        // This must be a qualified name starting with a reserved keyword.
1430
                        // We need to overwrite the previously set final token.
1431
                        --$newStackPtr;
×
1432
                    }
1433

1434
                    $finalTokens[$newStackPtr] = $newToken;
×
1435
                    $newStackPtr++;
×
1436
                    $stackPtr = $i;
×
1437

1438
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1439
                        $type    = $newToken['type'];
×
1440
                        $content = $newToken['content'];
×
1441
                        StatusWriter::write("* token $nameStart to $i ($content) retokenized to $type", 2);
×
1442
                    }
1443

1444
                    continue;
×
1445
                }//end if
1446
            }//end if
1447

1448
            /*
1449
                PHP 8.0 Attributes
1450
            */
1451

1452
            if (PHP_VERSION_ID < 80000
2,242✔
1453
                && $token[0] === T_COMMENT
2,242✔
1454
                && strpos($token[1], '#[') === 0
2,242✔
1455
            ) {
1456
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
×
1457
                if ($subTokens !== null) {
×
1458
                    array_splice($tokens, $stackPtr, 1, $subTokens);
×
1459
                    $numTokens = count($tokens);
×
1460

1461
                    $tokenIsArray = true;
×
1462
                    $token        = $tokens[$stackPtr];
×
1463
                } else {
1464
                    $token[0] = T_ATTRIBUTE;
×
1465
                }
1466
            }
1467

1468
            if ($tokenIsArray === true
2,242✔
1469
                && $token[0] === T_ATTRIBUTE
2,242✔
1470
            ) {
1471
                // Go looking for the close bracket.
1472
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
34✔
1473

1474
                $newToken            = [];
34✔
1475
                $newToken['code']    = T_ATTRIBUTE;
34✔
1476
                $newToken['type']    = 'T_ATTRIBUTE';
34✔
1477
                $newToken['content'] = '#[';
34✔
1478
                $finalTokens[$newStackPtr] = $newToken;
34✔
1479

1480
                $tokens[$bracketCloser]    = [];
34✔
1481
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
34✔
1482
                $tokens[$bracketCloser][1] = ']';
34✔
1483

1484
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
34✔
1485
                    StatusWriter::write("* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END", 2);
×
1486
                }
1487

1488
                $newStackPtr++;
34✔
1489
                continue;
34✔
1490
            }//end if
1491

1492
            /*
1493
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1494
                token and ensures that the colon after it is always T_COLON.
1495
            */
1496

1497
            if ($tokenIsArray === true
2,242✔
1498
                && ($token[0] === T_STRING
2,242✔
1499
                || preg_match(self::PHP_LABEL_REGEX, $token[1]) === 1)
2,242✔
1500
            ) {
1501
                // Get the next non-empty token.
1502
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,118✔
1503
                    if (is_array($tokens[$i]) === false
2,118✔
1504
                        || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
2,118✔
1505
                    ) {
1506
                        break;
2,118✔
1507
                    }
1508
                }
1509

1510
                if (isset($tokens[$i]) === true
2,118✔
1511
                    && is_array($tokens[$i]) === false
2,118✔
1512
                    && $tokens[$i] === ':'
2,118✔
1513
                ) {
1514
                    // Get the previous non-empty token.
1515
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,118✔
1516
                        if (is_array($tokens[$j]) === false
1,118✔
1517
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$j][0]]) === false
1,118✔
1518
                        ) {
1519
                            break;
1,118✔
1520
                        }
1521
                    }
1522

1523
                    if (is_array($tokens[$j]) === false
1,118✔
1524
                        && ($tokens[$j] === '('
1,118✔
1525
                        || $tokens[$j] === ',')
1,118✔
1526
                    ) {
1527
                        $newToken            = [];
460✔
1528
                        $newToken['code']    = T_PARAM_NAME;
460✔
1529
                        $newToken['type']    = 'T_PARAM_NAME';
460✔
1530
                        $newToken['content'] = $token[1];
460✔
1531
                        $finalTokens[$newStackPtr] = $newToken;
460✔
1532

1533
                        $newStackPtr++;
460✔
1534

1535
                        // Modify the original token stack so that future checks, like
1536
                        // determining T_COLON vs T_INLINE_ELSE can handle this correctly.
1537
                        $tokens[$stackPtr][0] = T_PARAM_NAME;
460✔
1538

1539
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
460✔
1540
                            $type = Tokens::tokenName($token[0]);
×
1541
                            StatusWriter::write("* token $stackPtr changed from $type to T_PARAM_NAME", 2);
×
1542
                        }
1543

1544
                        continue;
460✔
1545
                    }
1546
                }//end if
1547
            }//end if
1548

1549
            /*
1550
                "readonly" keyword for PHP < 8.1
1551
            */
1552

1553
            if ($tokenIsArray === true
2,242✔
1554
                && strtolower($token[1]) === 'readonly'
2,242✔
1555
                && (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,242✔
1556
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
2,242✔
1557
            ) {
1558
                // Get the next non-whitespace token.
1559
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
676✔
1560
                    if (is_array($tokens[$i]) === false
676✔
1561
                        || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
676✔
1562
                    ) {
1563
                        break;
676✔
1564
                    }
1565
                }
1566

1567
                $isReadonlyKeyword = false;
676✔
1568

1569
                if (isset($tokens[$i]) === false
676✔
1570
                    || $tokens[$i] !== '('
676✔
1571
                ) {
1572
                    $isReadonlyKeyword = true;
676✔
1573
                } else if ($tokens[$i] === '(') {
×
1574
                    /*
1575
                     * Skip over tokens which can be used in type declarations.
1576
                     * At this point, the only token types which need to be taken into consideration
1577
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1578
                     * and the union/intersection/dnf parentheses.
1579
                     */
1580

1581
                    $foundDNFParens = 1;
×
1582
                    $foundDNFPipe   = 0;
×
1583

1584
                    for (++$i; $i < $numTokens; $i++) {
×
1585
                        if (is_array($tokens[$i]) === true) {
×
1586
                            $tokenType = $tokens[$i][0];
×
1587
                        } else {
1588
                            $tokenType = $tokens[$i];
×
1589
                        }
1590

1591
                        if (isset(Tokens::EMPTY_TOKENS[$tokenType]) === true) {
×
1592
                            continue;
×
1593
                        }
1594

1595
                        if ($tokenType === '|') {
×
1596
                            ++$foundDNFPipe;
×
1597
                            continue;
×
1598
                        }
1599

1600
                        if ($tokenType === ')') {
×
1601
                            ++$foundDNFParens;
×
1602
                            continue;
×
1603
                        }
1604

1605
                        if ($tokenType === '(') {
×
1606
                            ++$foundDNFParens;
×
1607
                            continue;
×
1608
                        }
1609

1610
                        if ($tokenType === T_STRING
×
1611
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1612
                            || $tokenType === T_NAME_RELATIVE
×
1613
                            || $tokenType === T_NAME_QUALIFIED
×
1614
                            || $tokenType === T_ARRAY
×
1615
                            || $tokenType === T_NAMESPACE
×
1616
                            || $tokenType === T_NS_SEPARATOR
×
1617
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1618
                            || $tokenType === '&' // PHP < 8.0.
×
1619
                        ) {
1620
                            continue;
×
1621
                        }
1622

1623
                        // Reached the next token after.
1624
                        if (($foundDNFParens % 2) === 0
×
1625
                            && $foundDNFPipe >= 1
×
1626
                            && ($tokenType === T_VARIABLE
×
1627
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1628
                        ) {
1629
                            $isReadonlyKeyword = true;
×
1630
                        }
1631

1632
                        break;
×
1633
                    }//end for
1634
                }//end if
1635

1636
                if ($isReadonlyKeyword === true) {
676✔
1637
                    $finalTokens[$newStackPtr] = [
676✔
1638
                        'code'    => T_READONLY,
676✔
1639
                        'type'    => 'T_READONLY',
676✔
1640
                        'content' => $token[1],
676✔
1641
                    ];
676✔
1642
                    $newStackPtr++;
676✔
1643

1644
                    // Also modify the original token stack so that
1645
                    // future checks (like looking for T_NULLABLE) can
1646
                    // detect the T_READONLY token more easily.
1647
                    $tokens[$stackPtr][0] = T_READONLY;
676✔
1648
                    $token[0] = T_READONLY;
676✔
1649

1650
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
676✔
1651
                        StatusWriter::write("* token $stackPtr changed from $type to T_READONLY", 2);
×
1652
                    }
1653
                } else {
1654
                    $finalTokens[$newStackPtr] = [
×
1655
                        'code'    => T_STRING,
×
1656
                        'type'    => 'T_STRING',
×
1657
                        'content' => $token[1],
×
1658
                    ];
×
1659
                    $newStackPtr++;
×
1660

1661
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1662
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1663
                    }
1664
                }//end if
1665

1666
                continue;
676✔
1667
            }//end if
1668

1669
            /*
1670
                Deal with "yield from" in various PHP versions.
1671
            */
1672

1673
            if (PHP_VERSION_ID < 80300
2,242✔
1674
                && $tokenIsArray === true
2,242✔
1675
                && $token[0] === T_STRING
2,242✔
1676
                && strtolower($token[1]) === 'from'
2,242✔
1677
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
2,242✔
1678
            ) {
1679
                /*
1680
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1681
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1682
                    We want to keep the tokenization of the tokens between, but need to change the
1683
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1684
                */
1685

1686
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
×
1687
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
×
1688

1689
                $finalTokens[$newStackPtr] = [
×
1690
                    'code'    => T_YIELD_FROM,
×
1691
                    'type'    => 'T_YIELD_FROM',
×
1692
                    'content' => $token[1],
×
1693
                ];
×
1694
                $newStackPtr++;
×
1695

1696
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1697
                    StatusWriter::write("* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD", 2);
×
1698
                    StatusWriter::write("* token $stackPtr changed into T_YIELD_FROM; was: T_STRING", 2);
×
1699
                }
1700

1701
                continue;
×
1702
            } else if ($tokenIsArray === true
2,242✔
1703
                && $token[0] === T_YIELD_FROM
2,242✔
1704
                && strpos($token[1], $this->eolChar) !== false
2,242✔
1705
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
2,242✔
1706
            ) {
1707
                /*
1708
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1709
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1710
                    separately for consistency.
1711
                */
1712

1713
                $finalTokens[$newStackPtr] = [
36✔
1714
                    'code'    => T_YIELD_FROM,
36✔
1715
                    'type'    => 'T_YIELD_FROM',
36✔
1716
                    'content' => substr($token[1], 0, 5),
36✔
1717
                ];
36✔
1718
                $newStackPtr++;
36✔
1719

1720
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
36✔
1721
                $numLines   = count($tokenLines);
36✔
1722
                $newToken   = [
36✔
1723
                    'type'    => 'T_WHITESPACE',
36✔
1724
                    'code'    => T_WHITESPACE,
36✔
1725
                    'content' => '',
36✔
1726
                ];
36✔
1727

1728
                foreach ($tokenLines as $i => $line) {
36✔
1729
                    $newToken['content'] = $line;
36✔
1730
                    if ($i === ($numLines - 1)) {
36✔
1731
                        if ($line === '') {
36✔
1732
                            break;
×
1733
                        }
1734
                    } else {
1735
                        $newToken['content'] .= $this->eolChar;
36✔
1736
                    }
1737

1738
                    $finalTokens[$newStackPtr] = $newToken;
36✔
1739
                    $newStackPtr++;
36✔
1740
                }
1741

1742
                $finalTokens[$newStackPtr] = [
36✔
1743
                    'code'    => T_YIELD_FROM,
36✔
1744
                    'type'    => 'T_YIELD_FROM',
36✔
1745
                    'content' => substr($token[1], -4),
36✔
1746
                ];
36✔
1747
                $newStackPtr++;
36✔
1748

1749
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
36✔
1750
                    StatusWriter::write("* token $stackPtr split into 'yield', one or more whitespace tokens and 'from'", 2);
×
1751
                }
1752

1753
                continue;
36✔
1754
            } else if (PHP_VERSION_ID >= 80300
2,242✔
1755
                && $tokenIsArray === true
2,242✔
1756
                && $token[0] === T_YIELD_FROM
2,242✔
1757
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
2,242✔
1758
                && stripos($token[1], 'yield') === 0
2,242✔
1759
            ) {
1760
                /*
1761
                    Since PHP 8.3, "yield from" allows for comments and will
1762
                    swallow the comment in the `T_YIELD_FROM` token.
1763
                    We need to split this up to allow for sniffs handling comments.
1764
                */
1765

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

1773
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
36✔
1774
                // Remove the PHP open tag token.
1775
                array_shift($yieldFromSubtokens);
36✔
1776
                // Add the "from" keyword.
1777
                $yieldFromSubtokens[] = [
36✔
1778
                    0 => T_YIELD_FROM,
36✔
1779
                    1 => substr($token[1], -4),
36✔
1780
                ];
36✔
1781

1782
                // Inject the new tokens into the token stack.
1783
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
36✔
1784
                $numTokens = count($tokens);
36✔
1785

1786
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
36✔
1787
                    StatusWriter::write("* token $stackPtr split into parts (yield from with comment)", 2);
×
1788
                }
1789

1790
                unset($yieldFromSubtokens);
36✔
1791
                continue;
36✔
1792
            }//end if
1793

1794
            /*
1795
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1796
                T_COALESCE, T_EQUAL.
1797
                So look for and combine these tokens in earlier versions.
1798
            */
1799

1800
            if ($tokenIsArray === true
2,242✔
1801
                && $token[0] === T_COALESCE
2,242✔
1802
                && isset($tokens[($stackPtr + 1)]) === true
2,242✔
1803
                && $tokens[($stackPtr + 1)][0] === '='
2,242✔
1804
            ) {
1805
                $newToken            = [];
×
1806
                $newToken['code']    = T_COALESCE_EQUAL;
×
1807
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1808
                $newToken['content'] = '??=';
×
1809
                $finalTokens[$newStackPtr] = $newToken;
×
1810

1811
                $newStackPtr++;
×
1812
                $stackPtr++;
×
1813

1814
                continue;
×
1815
            }
1816

1817
            /*
1818
                Before PHP 8, the ?-> operator was tokenized as
1819
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1820
                So look for and combine these tokens in earlier versions.
1821
            */
1822

1823
            if ($tokenIsArray === false
2,242✔
1824
                && $token[0] === '?'
2,242✔
1825
                && isset($tokens[($stackPtr + 1)]) === true
2,242✔
1826
                && is_array($tokens[($stackPtr + 1)]) === true
2,242✔
1827
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
2,242✔
1828
            ) {
1829
                $newToken            = [];
×
1830
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
×
1831
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
×
1832
                $newToken['content'] = '?->';
×
1833
                $finalTokens[$newStackPtr] = $newToken;
×
1834

1835
                $newStackPtr++;
×
1836
                $stackPtr++;
×
1837
                continue;
×
1838
            }
1839

1840
            /*
1841
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1842
                tokens split the token with a T_STRING. So look for
1843
                and change these tokens in earlier versions.
1844
            */
1845

1846
            if (PHP_VERSION_ID < 70400
2,242✔
1847
                && ($tokenIsArray === true
2,242✔
1848
                && ($token[0] === T_LNUMBER
2,242✔
1849
                || $token[0] === T_DNUMBER)
2,242✔
1850
                && isset($tokens[($stackPtr + 1)]) === true
2,242✔
1851
                && is_array($tokens[($stackPtr + 1)]) === true
2,242✔
1852
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,242✔
1853
                && $tokens[($stackPtr + 1)][1][0] === '_')
2,242✔
1854
            ) {
1855
                $newContent = $token[1];
×
1856
                $newType    = $token[0];
×
1857
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
×
1858
                    if (is_array($tokens[$i]) === false) {
×
1859
                        break;
×
1860
                    }
1861

1862
                    if ($tokens[$i][0] === T_LNUMBER
×
1863
                        || $tokens[$i][0] === T_DNUMBER
×
1864
                    ) {
1865
                        $newContent .= $tokens[$i][1];
×
1866
                        continue;
×
1867
                    }
1868

1869
                    if ($tokens[$i][0] === T_STRING
×
1870
                        && $tokens[$i][1][0] === '_'
×
1871
                        && ((strpos($newContent, '0x') === 0
×
1872
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
×
1873
                        || (strpos($newContent, '0x') !== 0
×
1874
                        && substr($newContent, -1) !== '.'
×
1875
                        && substr(strtolower($newContent), -1) !== 'e'
×
1876
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
×
1877
                    ) {
1878
                        $newContent .= $tokens[$i][1];
×
1879

1880
                        // Support floats.
1881
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
×
1882
                            && ($tokens[($i + 1)] === '-'
×
1883
                            || $tokens[($i + 1)] === '+')
×
1884
                        ) {
1885
                            $newContent .= $tokens[($i + 1)];
×
1886
                            $i++;
×
1887
                        }
1888

1889
                        continue;
×
1890
                    }//end if
1891

1892
                    break;
×
1893
                }//end for
1894

1895
                if ($newType === T_LNUMBER
×
1896
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
×
1897
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
×
1898
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
×
1899
                    || (stripos($newContent, '0x') !== 0
×
1900
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
×
1901
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
×
1902
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
×
1903
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
×
1904
                ) {
1905
                    $newType = T_DNUMBER;
×
1906
                }
1907

1908
                $newToken            = [];
×
1909
                $newToken['code']    = $newType;
×
1910
                $newToken['type']    = Tokens::tokenName($newType);
×
1911
                $newToken['content'] = $newContent;
×
1912
                $finalTokens[$newStackPtr] = $newToken;
×
1913

1914
                $newStackPtr++;
×
1915
                $stackPtr = ($i - 1);
×
1916
                continue;
×
1917
            }//end if
1918

1919
            /*
1920
                Backfill the T_MATCH token for PHP versions < 8.0 and
1921
                do initial correction for non-match expression T_MATCH tokens
1922
                to T_STRING for PHP >= 8.0.
1923
                A final check for non-match expression T_MATCH tokens is done
1924
                in PHP::processAdditional().
1925
            */
1926

1927
            if ($tokenIsArray === true
2,242✔
1928
                && (($token[0] === T_STRING
2,242✔
1929
                && strtolower($token[1]) === 'match')
2,242✔
1930
                || $token[0] === T_MATCH)
2,242✔
1931
            ) {
1932
                $isMatch = false;
498✔
1933
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
498✔
1934
                    if (isset($tokens[$x][0], Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === true) {
498✔
1935
                        continue;
498✔
1936
                    }
1937

1938
                    if ($tokens[$x] !== '(') {
498✔
1939
                        // This is not a match expression.
1940
                        break;
122✔
1941
                    }
1942

1943
                    if (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
498✔
1944
                        // Also not a match expression.
1945
                        break;
122✔
1946
                    }
1947

1948
                    $isMatch = true;
498✔
1949
                    break;
498✔
1950
                }//end for
1951

1952
                if ($isMatch === true && $token[0] === T_STRING) {
498✔
1953
                    $newToken            = [];
×
1954
                    $newToken['code']    = T_MATCH;
×
1955
                    $newToken['type']    = 'T_MATCH';
×
1956
                    $newToken['content'] = $token[1];
×
1957

1958
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1959
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_MATCH", 2);
×
1960
                    }
1961

1962
                    $finalTokens[$newStackPtr] = $newToken;
×
1963
                    $newStackPtr++;
×
1964
                    continue;
×
1965
                } else if ($isMatch === false && $token[0] === T_MATCH) {
498✔
1966
                    // PHP 8.0, match keyword, but not a match expression.
1967
                    $newToken            = [];
122✔
1968
                    $newToken['code']    = T_STRING;
122✔
1969
                    $newToken['type']    = 'T_STRING';
122✔
1970
                    $newToken['content'] = $token[1];
122✔
1971

1972
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
122✔
1973
                        StatusWriter::write("* token $stackPtr changed from T_MATCH to T_STRING", 2);
×
1974
                    }
1975

1976
                    $finalTokens[$newStackPtr] = $newToken;
122✔
1977
                    $newStackPtr++;
122✔
1978
                    continue;
122✔
1979
                }//end if
1980
            }//end if
1981

1982
            /*
1983
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
1984
                to prevent scope being set and the scope for switch default statements
1985
                breaking.
1986
            */
1987

1988
            if ($tokenIsArray === true
2,242✔
1989
                && $token[0] === T_DEFAULT
2,242✔
1990
                && isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,242✔
1991
            ) {
1992
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
984✔
1993
                    if ($tokens[$x] === ',') {
984✔
1994
                        // Skip over potential trailing comma (supported in PHP).
1995
                        continue;
154✔
1996
                    }
1997

1998
                    if (is_array($tokens[$x]) === false
984✔
1999
                        || isset(Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === false
984✔
2000
                    ) {
2001
                        // Non-empty, non-comma content.
2002
                        break;
984✔
2003
                    }
2004
                }
2005

2006
                if (isset($tokens[$x]) === true
984✔
2007
                    && is_array($tokens[$x]) === true
984✔
2008
                    && $tokens[$x][0] === T_DOUBLE_ARROW
984✔
2009
                ) {
2010
                    // Modify the original token stack for the double arrow so that
2011
                    // future checks can disregard the double arrow token more easily.
2012
                    // For match expression "case" statements, this is handled
2013
                    // in PHP::processAdditional().
2014
                    $tokens[$x][0] = T_MATCH_ARROW;
498✔
2015
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
498✔
2016
                        StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
2017
                    }
2018

2019
                    $newToken            = [];
498✔
2020
                    $newToken['code']    = T_MATCH_DEFAULT;
498✔
2021
                    $newToken['type']    = 'T_MATCH_DEFAULT';
498✔
2022
                    $newToken['content'] = $token[1];
498✔
2023

2024
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
498✔
2025
                        StatusWriter::write("* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT", 2);
×
2026
                    }
2027

2028
                    $finalTokens[$newStackPtr] = $newToken;
498✔
2029
                    $newStackPtr++;
498✔
2030
                    continue;
498✔
2031
                }//end if
2032
            }//end if
2033

2034
            /*
2035
                Convert ? to T_NULLABLE OR T_INLINE_THEN
2036
            */
2037

2038
            if ($tokenIsArray === false && $token[0] === '?') {
2,242✔
2039
                $newToken            = [];
860✔
2040
                $newToken['content'] = '?';
860✔
2041

2042
                // For typed constants, we only need to check the token before the ? to be sure.
2043
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
860✔
2044
                    $newToken['code'] = T_NULLABLE;
126✔
2045
                    $newToken['type'] = 'T_NULLABLE';
126✔
2046

2047
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
126✔
2048
                        StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2049
                    }
2050

2051
                    $finalTokens[$newStackPtr] = $newToken;
126✔
2052
                    $newStackPtr++;
126✔
2053
                    continue;
126✔
2054
                }
2055

2056
                /*
2057
                 * Check if the next non-empty token is one of the tokens which can be used
2058
                 * in type declarations. If not, it's definitely a ternary.
2059
                 * At this point, the only token types which need to be taken into consideration
2060
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
2061
                 */
2062

2063
                $lastRelevantNonEmpty = null;
860✔
2064

2065
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
860✔
2066
                    if (is_array($tokens[$i]) === true) {
860✔
2067
                        $tokenType = $tokens[$i][0];
860✔
2068
                    } else {
2069
                        $tokenType = $tokens[$i];
598✔
2070
                    }
2071

2072
                    if (isset(Tokens::EMPTY_TOKENS[$tokenType]) === true) {
860✔
2073
                        continue;
860✔
2074
                    }
2075

2076
                    if (isset(Tokens::NAME_TOKENS[$tokenType]) === true
860✔
2077
                        || $tokenType === T_ARRAY
860✔
2078
                        || $tokenType === T_NAMESPACE
860✔
2079
                        || $tokenType === T_NS_SEPARATOR
860✔
2080
                    ) {
2081
                        $lastRelevantNonEmpty = $tokenType;
598✔
2082
                        continue;
598✔
2083
                    }
2084

2085
                    if (($tokenType !== T_CALLABLE
860✔
2086
                        && isset($lastRelevantNonEmpty) === false)
860✔
2087
                        || ($lastRelevantNonEmpty === T_ARRAY
598✔
2088
                        && $tokenType === '(')
598✔
2089
                        || (isset(Tokens::NAME_TOKENS[$lastRelevantNonEmpty]) === true
860✔
2090
                        && ($tokenType === T_DOUBLE_COLON
860✔
2091
                        || $tokenType === '('
860✔
2092
                        || $tokenType === ':'))
860✔
2093
                    ) {
2094
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
748✔
2095
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2096
                        }
2097

2098
                        $newToken['code'] = T_INLINE_THEN;
748✔
2099
                        $newToken['type'] = 'T_INLINE_THEN';
748✔
2100

2101
                        $insideInlineIf[] = $stackPtr;
748✔
2102

2103
                        $finalTokens[$newStackPtr] = $newToken;
748✔
2104
                        $newStackPtr++;
748✔
2105
                        continue 2;
748✔
2106
                    }
2107

2108
                    break;
112✔
2109
                }//end for
2110

2111
                /*
2112
                 * This can still be a nullable type or a ternary.
2113
                 * Do additional checking.
2114
                 */
2115

2116
                $prevNonEmpty     = null;
126✔
2117
                $lastSeenNonEmpty = null;
126✔
2118

2119
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
126✔
2120
                    if (is_array($tokens[$i]) === true) {
126✔
2121
                        $tokenType = $tokens[$i][0];
126✔
2122
                    } else {
2123
                        $tokenType = $tokens[$i];
126✔
2124
                    }
2125

2126
                    if ($tokenType === T_STATIC
126✔
2127
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
126✔
2128
                        || $lastSeenNonEmpty === '(')
126✔
2129
                    ) {
2130
                        $lastSeenNonEmpty = $tokenType;
×
2131
                        continue;
×
2132
                    }
2133

2134
                    if ($prevNonEmpty === null
126✔
2135
                        && isset(Tokens::EMPTY_TOKENS[$tokenType]) === false
126✔
2136
                    ) {
2137
                        // Found the previous non-empty token.
2138
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
126✔
2139
                            $newToken['code'] = T_NULLABLE;
100✔
2140
                            $newToken['type'] = 'T_NULLABLE';
100✔
2141

2142
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
100✔
2143
                                StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2144
                            }
2145

2146
                            break;
100✔
2147
                        }
2148

2149
                        $prevNonEmpty = $tokenType;
126✔
2150
                    }
2151

2152
                    if ($tokenType === T_FUNCTION
126✔
2153
                        || $tokenType === T_FN
126✔
2154
                        || isset(Tokens::METHOD_MODIFIERS[$tokenType]) === true
126✔
2155
                        || isset(Tokens::SCOPE_MODIFIERS[$tokenType]) === true
126✔
2156
                        || $tokenType === T_VAR
126✔
2157
                        || $tokenType === T_READONLY
126✔
2158
                    ) {
2159
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
112✔
2160
                            StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2161
                        }
2162

2163
                        $newToken['code'] = T_NULLABLE;
112✔
2164
                        $newToken['type'] = 'T_NULLABLE';
112✔
2165
                        break;
112✔
2166
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
126✔
2167
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
26✔
2168
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2169
                        }
2170

2171
                        $newToken['code'] = T_INLINE_THEN;
26✔
2172
                        $newToken['type'] = 'T_INLINE_THEN';
26✔
2173

2174
                        $insideInlineIf[] = $stackPtr;
26✔
2175
                        break;
26✔
2176
                    }
2177

2178
                    if (isset(Tokens::EMPTY_TOKENS[$tokenType]) === false) {
126✔
2179
                        $lastSeenNonEmpty = $tokenType;
126✔
2180
                    }
2181
                }//end for
2182

2183
                $finalTokens[$newStackPtr] = $newToken;
126✔
2184
                $newStackPtr++;
126✔
2185
                continue;
126✔
2186
            }//end if
2187

2188
            /*
2189
                Tokens after a double colon may look like scope openers,
2190
                such as when writing code like Foo::NAMESPACE, but they are
2191
                only ever variables or strings.
2192
            */
2193

2194
            if ($stackPtr > 1
2,242✔
2195
                && (is_array($tokens[($stackPtr - 1)]) === true
2,242✔
2196
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
2,242✔
2197
                && $tokenIsArray === true
2,242✔
2198
                && $token[0] !== T_STRING
2,242✔
2199
                && $token[0] !== T_VARIABLE
2,242✔
2200
                && $token[0] !== T_DOLLAR
2,242✔
2201
                && isset(Tokens::EMPTY_TOKENS[$token[0]]) === false
2,242✔
2202
            ) {
2203
                $newToken            = [];
×
2204
                $newToken['code']    = T_STRING;
×
2205
                $newToken['type']    = 'T_STRING';
×
2206
                $newToken['content'] = $token[1];
×
2207
                $finalTokens[$newStackPtr] = $newToken;
×
2208

2209
                $newStackPtr++;
×
2210
                continue;
×
2211
            }
2212

2213
            /*
2214
                Backfill the T_FN token for PHP versions < 7.4.
2215
            */
2216

2217
            if ($tokenIsArray === true
2,242✔
2218
                && $token[0] === T_STRING
2,242✔
2219
                && strtolower($token[1]) === 'fn'
2,242✔
2220
                && isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,242✔
2221
                && $finalTokens[$lastNotEmptyToken]['content'] !== '&'
2,242✔
2222
            ) {
2223
                // Modify the original token stack so that
2224
                // future checks (like looking for T_NULLABLE) can
2225
                // detect the T_FN token more easily.
2226
                $tokens[$stackPtr][0] = T_FN;
×
2227
                $token[0] = T_FN;
×
2228
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2229
                    StatusWriter::write("* token $stackPtr changed from T_STRING to T_FN", 2);
×
2230
                }
2231
            }
2232

2233
            /*
2234
                This is a special condition for T_ARRAY tokens used for
2235
                function return types. We want to keep the parenthesis map clean,
2236
                so let's tag these tokens as T_STRING.
2237
            */
2238

2239
            if ($tokenIsArray === true
2,242✔
2240
                && ($token[0] === T_FUNCTION
2,242✔
2241
                || $token[0] === T_FN)
2,242✔
2242
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
2,242✔
2243
            ) {
2244
                // Go looking for the colon to start the return type hint.
2245
                // Start by finding the closing parenthesis of the function.
2246
                $parenthesisStack  = [];
1,744✔
2247
                $parenthesisCloser = false;
1,744✔
2248
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,744✔
2249
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
1,744✔
2250
                        $parenthesisStack[] = $x;
1,744✔
2251
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
1,744✔
2252
                        array_pop($parenthesisStack);
1,744✔
2253
                        if (empty($parenthesisStack) === true) {
1,744✔
2254
                            $parenthesisCloser = $x;
1,744✔
2255
                            break;
1,744✔
2256
                        }
2257
                    }
2258
                }
2259

2260
                if ($parenthesisCloser !== false) {
1,744✔
2261
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
1,744✔
2262
                        if (is_array($tokens[$x]) === false
1,744✔
2263
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === false
1,744✔
2264
                        ) {
2265
                            // Non-empty content.
2266
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
1,744✔
2267
                                // Found a use statement, so search ahead for the closing parenthesis.
2268
                                for ($x += 1; $x < $numTokens; $x++) {
38✔
2269
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
38✔
2270
                                        continue(2);
38✔
2271
                                    }
2272
                                }
2273
                            }
2274

2275
                            break;
1,744✔
2276
                        }
2277
                    }
2278

2279
                    if (isset($tokens[$x]) === true
1,744✔
2280
                        && is_array($tokens[$x]) === false
1,744✔
2281
                        && $tokens[$x] === ':'
1,744✔
2282
                    ) {
2283
                        // Find the start of the return type.
2284
                        for ($x += 1; $x < $numTokens; $x++) {
1,314✔
2285
                            if (is_array($tokens[$x]) === true
1,314✔
2286
                                && isset(Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === true
1,314✔
2287
                            ) {
2288
                                // Whitespace or comments before the return type.
2289
                                continue;
1,314✔
2290
                            }
2291

2292
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1,314✔
2293
                                // Found a nullable operator, so skip it.
2294
                                // But also convert the token to save the tokenizer
2295
                                // a bit of time later on.
2296
                                $tokens[$x] = [
100✔
2297
                                    T_NULLABLE,
100✔
2298
                                    '?',
100✔
2299
                                ];
100✔
2300

2301
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
100✔
2302
                                    StatusWriter::write("* token $x changed from ? to T_NULLABLE", 2);
×
2303
                                }
2304

2305
                                continue;
100✔
2306
                            }
2307

2308
                            break;
1,314✔
2309
                        }//end for
2310
                    }//end if
2311
                }//end if
2312
            }//end if
2313

2314
            /*
2315
                PHP doesn't assign a token to goto labels, so we have to.
2316
                These are just string tokens with a single colon after them. Double
2317
                colons are already tokenized and so don't interfere with this check.
2318
                But we do have to account for CASE statements, that look just like
2319
                goto labels.
2320
            */
2321

2322
            if ($tokenIsArray === true
2,242✔
2323
                && $token[0] === T_STRING
2,242✔
2324
                && (is_array($tokens[($stackPtr - 1)]) === false
2,242✔
2325
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
2,242✔
2326
            ) {
2327
                // Find next non-empty token.
2328
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,972✔
2329
                    if (is_array($tokens[$i]) === true
1,972✔
2330
                        && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
1,972✔
2331
                    ) {
2332
                        continue;
1,756✔
2333
                    }
2334

2335
                    break;
1,972✔
2336
                }
2337

2338
                if (isset($tokens[$i]) === true && $tokens[$i] === ':') {
1,972✔
2339
                    // Okay, so we have a colon, now we need to make sure that this is not
2340
                    // class constant access, a ternary, enum or switch case.
2341
                    $stopTokens = [
962✔
2342
                        T_DOUBLE_COLON       => true,
962✔
2343
                        T_CASE               => true,
962✔
2344
                        T_SEMICOLON          => true,
962✔
2345
                        T_OPEN_TAG           => true,
962✔
2346
                        T_OPEN_CURLY_BRACKET => true,
962✔
2347
                        T_INLINE_THEN        => true,
962✔
2348
                        T_ENUM               => true,
962✔
2349
                    ];
962✔
2350

2351
                    for ($x = ($newStackPtr - 1); $x > 0; $x--) {
962✔
2352
                        if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
962✔
2353
                            break;
962✔
2354
                        }
2355
                    }
2356

2357
                    if ($finalTokens[$x]['code'] !== T_DOUBLE_COLON
962✔
2358
                        && $finalTokens[$x]['code'] !== T_CASE
962✔
2359
                        && $finalTokens[$x]['code'] !== T_INLINE_THEN
962✔
2360
                        && $finalTokens[$x]['code'] !== T_ENUM
962✔
2361
                    ) {
2362
                        $finalTokens[$newStackPtr] = [
102✔
2363
                            'content' => $token[1],
102✔
2364
                            'code'    => T_GOTO_LABEL,
102✔
2365
                            'type'    => 'T_GOTO_LABEL',
102✔
2366
                        ];
102✔
2367

2368
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
2369
                            StatusWriter::write("* token $stackPtr changed from T_STRING to T_GOTO_LABEL", 2);
×
2370
                        }
2371

2372
                        // Modify the original token stack for the colon as potential
2373
                        // whitespace/comments between still needs to get the normal treatment.
2374
                        $tokens[$i] = [
102✔
2375
                            0 => T_GOTO_COLON,
102✔
2376
                            1 => ':',
102✔
2377
                        ];
102✔
2378

2379
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
2380
                            StatusWriter::write("* token $i changed from \":\" to T_GOTO_COLON in the original token stack", 2);
×
2381
                        }
2382

2383
                        $newStackPtr++;
102✔
2384
                        continue;
102✔
2385
                    }//end if
2386
                }//end if
2387
            }//end if
2388

2389
            /*
2390
                If this token has newlines in its content, split each line up
2391
                and create a new token for each line. We do this so it's easier
2392
                to ascertain where errors occur on a line.
2393
                Note that $token[1] is the token's content.
2394
            */
2395

2396
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
2,242✔
2397
                $tokenLines = explode($this->eolChar, $token[1]);
2,242✔
2398
                $numLines   = count($tokenLines);
2,242✔
2399
                $newToken   = [
2,242✔
2400
                    'type'    => Tokens::tokenName($token[0]),
2,242✔
2401
                    'code'    => $token[0],
2,242✔
2402
                    'content' => '',
2,242✔
2403
                ];
2,242✔
2404

2405
                for ($i = 0; $i < $numLines; $i++) {
2,242✔
2406
                    $newToken['content'] = $tokenLines[$i];
2,242✔
2407
                    if ($i === ($numLines - 1)) {
2,242✔
2408
                        if ($tokenLines[$i] === '') {
2,242✔
2409
                            break;
2,242✔
2410
                        }
2411
                    } else {
2412
                        $newToken['content'] .= $this->eolChar;
2,242✔
2413
                    }
2414

2415
                    $finalTokens[$newStackPtr] = $newToken;
2,242✔
2416
                    $newStackPtr++;
2,242✔
2417
                }
2418
            } else {
2419
                // Some T_STRING tokens should remain that way due to their context.
2420
                if ($tokenIsArray === true && $token[0] === T_STRING) {
2,242✔
2421
                    $preserveTstring = false;
1,972✔
2422

2423
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2424
                    // but the constant name should not be.
2425
                    if ((isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
1,972✔
2426
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
1,896✔
2427
                        || $insideConstDeclaration === true
1,972✔
2428
                    ) {
2429
                        // Find the next non-empty token.
2430
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,050✔
2431
                            if (is_array($tokens[$i]) === true
1,050✔
2432
                                && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
1,050✔
2433
                            ) {
2434
                                continue;
950✔
2435
                            }
2436

2437
                            break;
1,050✔
2438
                        }
2439

2440
                        if ($tokens[$i] === '=') {
1,050✔
2441
                            $preserveTstring        = true;
914✔
2442
                            $insideConstDeclaration = false;
914✔
2443
                        }
2444
                    } else if (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
1,972✔
2445
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
1,972✔
2446
                    ) {
2447
                        $preserveTstring = true;
1,896✔
2448

2449
                        // Special case for syntax like: return new self/new parent
2450
                        // where self/parent should not be a string.
2451
                        $tokenContentLower = strtolower($token[1]);
1,896✔
2452
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
1,896✔
2453
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
1,896✔
2454
                        ) {
2455
                            $preserveTstring = false;
746✔
2456
                        }
2457
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
1,812✔
2458
                        // Function names for functions declared to return by reference.
2459
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
702✔
2460
                            if (isset(Tokens::EMPTY_TOKENS[$finalTokens[$i]['code']]) === true) {
702✔
2461
                                continue;
320✔
2462
                            }
2463

2464
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
702✔
2465
                                $preserveTstring = true;
320✔
2466
                            }
2467

2468
                            break;
702✔
2469
                        }
2470
                    } else {
2471
                        // Keywords with special PHPCS token when used as a function call.
2472
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,812✔
2473
                            if (is_array($tokens[$i]) === true
1,812✔
2474
                                && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
1,812✔
2475
                            ) {
2476
                                continue;
1,542✔
2477
                            }
2478

2479
                            if ($tokens[$i][0] === '(') {
1,812✔
2480
                                $preserveTstring = true;
1,520✔
2481
                            }
2482

2483
                            break;
1,812✔
2484
                        }
2485
                    }//end if
2486

2487
                    if ($preserveTstring === true) {
1,972✔
2488
                        $finalTokens[$newStackPtr] = [
1,898✔
2489
                            'code'    => T_STRING,
1,898✔
2490
                            'type'    => 'T_STRING',
1,898✔
2491
                            'content' => $token[1],
1,898✔
2492
                        ];
1,898✔
2493

2494
                        $newStackPtr++;
1,898✔
2495
                        continue;
1,898✔
2496
                    }
2497
                }//end if
2498

2499
                $newToken = null;
2,242✔
2500
                if ($tokenIsArray === false) {
2,242✔
2501
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
2,234✔
2502
                        $newToken = self::$resolveTokenCache[$token[0]];
2,234✔
2503
                    }
2504
                } else {
2505
                    $cacheKey = null;
2,242✔
2506
                    if ($token[0] === T_STRING) {
2,242✔
2507
                        $cacheKey = strtolower($token[1]);
1,810✔
2508
                    } else if ($token[0] !== T_CURLY_OPEN) {
2,242✔
2509
                        $cacheKey = $token[0];
2,242✔
2510
                    }
2511

2512
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
2,242✔
2513
                        $newToken            = self::$resolveTokenCache[$cacheKey];
2,242✔
2514
                        $newToken['content'] = $token[1];
2,242✔
2515
                    }
2516
                }
2517

2518
                if ($newToken === null) {
2,242✔
2519
                    $newToken = self::standardiseToken($token);
×
2520
                }
2521

2522
                // Convert colons that are actually the ELSE component of an
2523
                // inline IF statement.
2524
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
2,242✔
2525
                    $isInlineIf = true;
748✔
2526

2527
                    // Make sure this isn't a named parameter label.
2528
                    // Get the previous non-empty token.
2529
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
748✔
2530
                        if (is_array($tokens[$i]) === false
748✔
2531
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
748✔
2532
                        ) {
2533
                            break;
748✔
2534
                        }
2535
                    }
2536

2537
                    if ($tokens[$i][0] === T_PARAM_NAME) {
748✔
2538
                        $isInlineIf = false;
426✔
2539
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
426✔
2540
                            StatusWriter::write('* token is parameter label, not T_INLINE_ELSE', 2);
×
2541
                        }
2542
                    }
2543

2544
                    if ($isInlineIf === true) {
748✔
2545
                        // Make sure this isn't a return type separator.
2546
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
748✔
2547
                            if (is_array($tokens[$i]) === false
748✔
2548
                                || ($tokens[$i][0] !== T_DOC_COMMENT
748✔
2549
                                && $tokens[$i][0] !== T_COMMENT
748✔
2550
                                && $tokens[$i][0] !== T_WHITESPACE)
748✔
2551
                            ) {
2552
                                break;
748✔
2553
                            }
2554
                        }
2555

2556
                        if ($tokens[$i] === ')') {
748✔
2557
                            $parenCount = 1;
426✔
2558
                            for ($i--; $i > 0; $i--) {
426✔
2559
                                if ($tokens[$i] === '(') {
426✔
2560
                                    $parenCount--;
426✔
2561
                                    if ($parenCount === 0) {
426✔
2562
                                        break;
426✔
2563
                                    }
2564
                                } else if ($tokens[$i] === ')') {
426✔
2565
                                    $parenCount++;
×
2566
                                }
2567
                            }
2568

2569
                            // We've found the open parenthesis, so if the previous
2570
                            // non-empty token is FUNCTION or USE, this is a return type.
2571
                            // Note that we need to skip T_STRING tokens here as these
2572
                            // can be function names.
2573
                            for ($i--; $i > 0; $i--) {
426✔
2574
                                if (is_array($tokens[$i]) === false
426✔
2575
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
426✔
2576
                                    && $tokens[$i][0] !== T_COMMENT
426✔
2577
                                    && $tokens[$i][0] !== T_WHITESPACE
426✔
2578
                                    && $tokens[$i][0] !== T_STRING)
426✔
2579
                                ) {
2580
                                    break;
426✔
2581
                                }
2582
                            }
2583

2584
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
426✔
2585
                                $isInlineIf = false;
426✔
2586
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
426✔
2587
                                    StatusWriter::write('* token is return type, not T_INLINE_ELSE', 2);
×
2588
                                }
2589
                            }
2590
                        }//end if
2591
                    }//end if
2592

2593
                    // Check to see if this is a CASE or DEFAULT opener.
2594
                    if ($isInlineIf === true) {
748✔
2595
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
748✔
2596
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
748✔
2597
                            if (is_array($tokens[$i]) === true
748✔
2598
                                && ($tokens[$i][0] === T_CASE
748✔
2599
                                || $tokens[$i][0] === T_DEFAULT)
748✔
2600
                            ) {
2601
                                $isInlineIf = false;
×
2602
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2603
                                    StatusWriter::write('* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE', 2);
×
2604
                                }
2605

2606
                                break;
×
2607
                            }
2608

2609
                            if (is_array($tokens[$i]) === false
748✔
2610
                                && ($tokens[$i] === ';'
748✔
2611
                                || $tokens[$i] === '{'
748✔
2612
                                || $tokens[$i] === '}')
748✔
2613
                            ) {
2614
                                break;
548✔
2615
                            }
2616
                        }//end for
2617
                    }//end if
2618

2619
                    if ($isInlineIf === true) {
748✔
2620
                        array_pop($insideInlineIf);
748✔
2621
                        $newToken['code'] = T_INLINE_ELSE;
748✔
2622
                        $newToken['type'] = 'T_INLINE_ELSE';
748✔
2623

2624
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
748✔
2625
                            StatusWriter::write('* token changed from T_COLON to T_INLINE_ELSE', 2);
×
2626
                        }
2627
                    }
2628
                }//end if
2629

2630
                // This is a special condition for T_ARRAY tokens used for anything else
2631
                // but array declarations, like type hinting function arguments as
2632
                // being arrays.
2633
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2634
                // T_STRING.
2635
                if ($newToken['code'] === T_ARRAY) {
2,242✔
2636
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,016✔
2637
                        if (is_array($tokens[$i]) === false
1,016✔
2638
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
1,016✔
2639
                        ) {
2640
                            // Non-empty content.
2641
                            break;
1,016✔
2642
                        }
2643
                    }
2644

2645
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,016✔
2646
                        $newToken['code'] = T_STRING;
862✔
2647
                        $newToken['type'] = 'T_STRING';
862✔
2648
                    }
2649
                }
2650

2651
                // This is a special case for PHP 5.6 use function and use const
2652
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2653
                // and T_CONST.
2654
                if (($newToken['code'] === T_FUNCTION
2,242✔
2655
                    || $newToken['code'] === T_CONST)
2,242✔
2656
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
2,242✔
2657
                ) {
2658
                    $newToken['code'] = T_STRING;
100✔
2659
                    $newToken['type'] = 'T_STRING';
100✔
2660
                }
2661

2662
                // This is a special case for use groups in PHP 7+ where leaving
2663
                // the curly braces as their normal tokens would confuse
2664
                // the scope map and sniffs.
2665
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
2,242✔
2666
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
2,242✔
2667
                ) {
2668
                    $newToken['code'] = T_OPEN_USE_GROUP;
100✔
2669
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
100✔
2670
                    $insideUseGroup   = true;
100✔
2671
                }
2672

2673
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
2,242✔
2674
                    $newToken['code'] = T_CLOSE_USE_GROUP;
100✔
2675
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
100✔
2676
                    $insideUseGroup   = false;
100✔
2677
                }
2678

2679
                $finalTokens[$newStackPtr] = $newToken;
2,242✔
2680
                $newStackPtr++;
2,242✔
2681
            }//end if
2682
        }//end for
2683

2684
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,242✔
2685
            StatusWriter::write('*** END PHP TOKENIZING ***', 1);
×
2686
        }
2687

2688
        return $finalTokens;
2,242✔
2689

2690
    }//end tokenize()
2691

2692

2693
    /**
2694
     * Performs additional processing after main tokenizing.
2695
     *
2696
     * This additional processing checks for CASE statements that are using curly
2697
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2698
     * into T_CLOSURE when they are not standard function definitions. It also
2699
     * detects short array syntax and converts those square brackets into new tokens.
2700
     * It also corrects some usage of the static and class keywords. It also
2701
     * assigns tokens to function return types.
2702
     *
2703
     * @return void
2704
     */
2705
    protected function processAdditional()
1,296✔
2706
    {
2707
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,296✔
2708
            StatusWriter::write('*** START ADDITIONAL PHP PROCESSING ***', 1);
×
2709
        }
2710

2711
        $this->createAttributesNestingMap();
1,296✔
2712

2713
        $numTokens         = count($this->tokens);
1,296✔
2714
        $lastSeenTypeToken = $numTokens;
1,296✔
2715

2716
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,296✔
2717
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2718
            if (isset($this->tokens[$i]['scope_opener']) === true
1,296✔
2719
                && isset($this->tokens[$i]['scope_condition']) === false
1,296✔
2720
            ) {
2721
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2722
            }
2723

2724
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,296✔
2725
                /*
2726
                    Detect functions that are actually closures and
2727
                    assign them a different token.
2728
                */
2729

2730
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,094✔
2731
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,094✔
2732
                        if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false
1,094✔
2733
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,094✔
2734
                        ) {
2735
                            break;
1,094✔
2736
                        }
2737
                    }
2738

2739
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,094✔
2740
                        $this->tokens[$i]['code'] = T_CLOSURE;
768✔
2741
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
768✔
2742
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
768✔
2743
                            $line = $this->tokens[$i]['line'];
×
2744
                            StatusWriter::write("* token $i on line $line changed from T_FUNCTION to T_CLOSURE", 1);
×
2745
                        }
2746

2747
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
768✔
2748
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
456✔
2749
                                continue;
×
2750
                            }
2751

2752
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
456✔
2753
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
456✔
2754
                                $type = $this->tokens[$x]['type'];
×
2755
                                StatusWriter::write("* cleaned $x ($type) *", 2);
×
2756
                            }
2757
                        }
2758
                    }
2759
                }//end if
2760

2761
                continue;
1,094✔
2762
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,296✔
2763
                /*
2764
                    Detect anonymous classes and assign them a different token.
2765
                */
2766

2767
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,196✔
2768
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
1,196✔
2769
                        break;
1,196✔
2770
                    }
2771
                }
2772

2773
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,196✔
2774
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,196✔
2775
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,178✔
2776
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,196✔
2777
                ) {
2778
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
842✔
2779
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
842✔
2780
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
842✔
2781
                        $line = $this->tokens[$i]['line'];
×
2782
                        StatusWriter::write("* token $i on line $line changed from T_CLASS to T_ANON_CLASS", 1);
×
2783
                    }
2784

2785
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
842✔
2786
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
842✔
2787
                    ) {
2788
                        $closer = $this->tokens[$x]['parenthesis_closer'];
530✔
2789

2790
                        $this->tokens[$i]['parenthesis_opener']     = $x;
530✔
2791
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
530✔
2792
                        $this->tokens[$i]['parenthesis_owner']      = $i;
530✔
2793
                        $this->tokens[$x]['parenthesis_owner']      = $i;
530✔
2794
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
530✔
2795

2796
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
530✔
2797
                            $line = $this->tokens[$i]['line'];
×
2798
                            StatusWriter::write("* added parenthesis keys to T_ANON_CLASS token $i on line $line", 2);
×
2799
                        }
2800
                    }
2801

2802
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
842✔
2803
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
760✔
2804
                            continue;
×
2805
                        }
2806

2807
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
760✔
2808
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
760✔
2809
                            $type = $this->tokens[$x]['type'];
×
2810
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
2811
                        }
2812
                    }
2813
                }//end if
2814

2815
                continue;
1,196✔
2816
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,296✔
2817
                // Possible arrow function.
2818
                for ($x = ($i + 1); $x < $numTokens; $x++) {
824✔
2819
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false
824✔
2820
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
824✔
2821
                    ) {
2822
                        // Non-whitespace content.
2823
                        break;
824✔
2824
                    }
2825
                }
2826

2827
                if (isset($this->tokens[$x]) === true
824✔
2828
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
824✔
2829
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
824✔
2830
                ) {
2831
                    $ignore  = Tokens::EMPTY_TOKENS;
822✔
2832
                    $ignore += Tokens::NAME_TOKENS;
822✔
2833
                    $ignore += [
822✔
2834
                        T_ARRAY                  => T_ARRAY,
822✔
2835
                        T_CALLABLE               => T_CALLABLE,
822✔
2836
                        T_COLON                  => T_COLON,
822✔
2837
                        T_NULL                   => T_NULL,
822✔
2838
                        T_TRUE                   => T_TRUE,
822✔
2839
                        T_FALSE                  => T_FALSE,
822✔
2840
                        T_NULLABLE               => T_NULLABLE,
822✔
2841
                        T_PARENT                 => T_PARENT,
822✔
2842
                        T_SELF                   => T_SELF,
822✔
2843
                        T_STATIC                 => T_STATIC,
822✔
2844
                        T_TYPE_UNION             => T_TYPE_UNION,
822✔
2845
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
822✔
2846
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
822✔
2847
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
822✔
2848
                    ];
822✔
2849

2850
                    $closer = $this->tokens[$x]['parenthesis_closer'];
822✔
2851
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
822✔
2852
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
822✔
2853
                            break;
822✔
2854
                        }
2855
                    }
2856

2857
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
822✔
2858
                        $endTokens = [
822✔
2859
                            T_COLON                => true,
822✔
2860
                            T_COMMA                => true,
822✔
2861
                            T_SEMICOLON            => true,
822✔
2862
                            T_CLOSE_PARENTHESIS    => true,
822✔
2863
                            T_CLOSE_SQUARE_BRACKET => true,
822✔
2864
                            T_CLOSE_CURLY_BRACKET  => true,
822✔
2865
                            T_CLOSE_SHORT_ARRAY    => true,
822✔
2866
                            T_OPEN_TAG             => true,
822✔
2867
                            T_CLOSE_TAG            => true,
822✔
2868
                        ];
822✔
2869

2870
                        $inTernary    = false;
822✔
2871
                        $lastEndToken = null;
822✔
2872

2873
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
822✔
2874
                            // Arrow function closer should never be shared with the closer of a match
2875
                            // control structure.
2876
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
822✔
2877
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
822✔
2878
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
822✔
2879
                            ) {
2880
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
292✔
2881
                                    // Match in return value of arrow function. Move on to the next token.
2882
                                    continue;
292✔
2883
                                }
2884

2885
                                // Arrow function as return value for the last match case without trailing comma.
2886
                                if ($lastEndToken !== null) {
292✔
2887
                                    $scopeCloser = $lastEndToken;
292✔
2888
                                    break;
292✔
2889
                                }
2890

2891
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
134✔
2892
                                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$lastNonEmpty]['code']]) === false) {
134✔
2893
                                        $scopeCloser = $lastNonEmpty;
134✔
2894
                                        break 2;
134✔
2895
                                    }
2896
                                }
2897
                            }
2898

2899
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
822✔
2900
                                if ($lastEndToken !== null
822✔
2901
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
822✔
2902
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
822✔
2903
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
822✔
2904
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
822✔
2905
                                ) {
2906
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
134✔
2907
                                        if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$lastNonEmpty]['code']]) === false) {
134✔
2908
                                            $scopeCloser = $lastNonEmpty;
134✔
2909
                                            break;
134✔
2910
                                        }
2911
                                    }
2912
                                }
2913

2914
                                break;
822✔
2915
                            }
2916

2917
                            if ($inTernary === false
822✔
2918
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
822✔
2919
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
822✔
2920
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
822✔
2921
                            ) {
2922
                                // Found a nested arrow function that already has the closer set and is in
2923
                                // the same scope as us, so we can use its closer.
2924
                                break;
134✔
2925
                            }
2926

2927
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
822✔
2928
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
822✔
2929
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
822✔
2930
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
822✔
2931
                            ) {
2932
                                // We minus 1 here in case the closer can be shared with us.
2933
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
292✔
2934
                                continue;
292✔
2935
                            }
2936

2937
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
822✔
2938
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
510✔
2939
                                $lastEndToken = $scopeCloser;
510✔
2940
                                continue;
510✔
2941
                            }
2942

2943
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
822✔
2944
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
134✔
2945
                                $lastEndToken = $scopeCloser;
134✔
2946
                                continue;
134✔
2947
                            }
2948

2949
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
822✔
2950
                                $inTernary = true;
134✔
2951
                                continue;
134✔
2952
                            }
2953

2954
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
822✔
2955
                                if ($inTernary === false) {
134✔
2956
                                    break;
134✔
2957
                                }
2958

2959
                                $inTernary = false;
134✔
2960
                                continue;
134✔
2961
                            }
2962
                        }//end for
2963

2964
                        if ($scopeCloser !== $numTokens) {
822✔
2965
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
822✔
2966
                                $line = $this->tokens[$i]['line'];
×
2967
                                StatusWriter::write("=> token $i on line $line processed as arrow function", 1);
×
2968
                                StatusWriter::write("* scope opener set to $arrow *", 2);
×
2969
                                StatusWriter::write("* scope closer set to $scopeCloser *", 2);
×
2970
                                StatusWriter::write("* parenthesis opener set to $x *", 2);
×
2971
                                StatusWriter::write("* parenthesis closer set to $closer *", 2);
×
2972
                            }
2973

2974
                            $this->tokens[$i]['code']            = T_FN;
822✔
2975
                            $this->tokens[$i]['type']            = 'T_FN';
822✔
2976
                            $this->tokens[$i]['scope_condition'] = $i;
822✔
2977
                            $this->tokens[$i]['scope_opener']    = $arrow;
822✔
2978
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
822✔
2979
                            $this->tokens[$i]['parenthesis_owner']  = $i;
822✔
2980
                            $this->tokens[$i]['parenthesis_opener'] = $x;
822✔
2981
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
822✔
2982

2983
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
822✔
2984
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
822✔
2985

2986
                            $this->tokens[$arrow]['scope_condition']       = $i;
822✔
2987
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
822✔
2988
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
822✔
2989
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
822✔
2990
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
822✔
2991
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
822✔
2992

2993
                            $opener = $this->tokens[$i]['parenthesis_opener'];
822✔
2994
                            $closer = $this->tokens[$i]['parenthesis_closer'];
822✔
2995
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
822✔
2996
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
822✔
2997

2998
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
822✔
2999
                                $line = $this->tokens[$arrow]['line'];
×
3000
                                StatusWriter::write("* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW", 2);
×
3001
                            }
3002
                        }//end if
3003
                    }//end if
3004
                }//end if
3005

3006
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
3007
                if (isset($this->tokens[$i]['scope_closer']) === false) {
824✔
3008
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
136✔
3009
                        $line = $this->tokens[$i]['line'];
×
3010
                        StatusWriter::write("=> token $i on line $line is not an arrow function", 1);
×
3011
                        StatusWriter::write('* token changed from T_FN to T_STRING', 2);
×
3012
                    }
3013

3014
                    $this->tokens[$i]['code'] = T_STRING;
136✔
3015
                    $this->tokens[$i]['type'] = 'T_STRING';
136✔
3016
                }
3017
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,296✔
3018
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
840✔
3019
                    continue;
82✔
3020
                }
3021

3022
                // Unless there is a variable or a bracket before this token,
3023
                // it is the start of an array being defined using the short syntax.
3024
                $isShortArray = false;
840✔
3025
                $allowed      = Tokens::NAME_TOKENS;
840✔
3026
                $allowed     += [
840✔
3027
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
840✔
3028
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
840✔
3029
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
840✔
3030
                    T_VARIABLE                 => T_VARIABLE,
840✔
3031
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
840✔
3032
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
840✔
3033
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
840✔
3034
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
840✔
3035
                ];
840✔
3036
                $allowed     += Tokens::MAGIC_CONSTANTS;
840✔
3037

3038
                for ($x = ($i - 1); $x >= 0; $x--) {
840✔
3039
                    // Allow for PHP 8.4 anon class dereferencing without wrapping parentheses.
3040
                    // Note: the T_CLASS token has not yet been retokenized to T_ANON_CLASS!
3041
                    if ($this->tokens[$x]['code'] === T_CLOSE_CURLY_BRACKET
840✔
3042
                        && isset($this->tokens[$x]['scope_condition']) === true
840✔
3043
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_CLASS
840✔
3044
                    ) {
3045
                        // Now, make sure it is an anonymous class and not a normal class.
3046
                        for ($y = ($this->tokens[$x]['scope_condition'] + 1); $y < $numTokens; $y++) {
82✔
3047
                            if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false) {
82✔
3048
                                break;
82✔
3049
                            }
3050
                        }
3051

3052
                        // Use the same check as used for the anon class retokenization.
3053
                        if ($this->tokens[$y]['code'] === T_OPEN_PARENTHESIS
82✔
3054
                            || $this->tokens[$y]['code'] === T_OPEN_CURLY_BRACKET
82✔
3055
                            || $this->tokens[$y]['code'] === T_EXTENDS
82✔
3056
                            || $this->tokens[$y]['code'] === T_IMPLEMENTS
82✔
3057
                        ) {
3058
                            break;
82✔
3059
                        }
3060
                    }
3061

3062
                    // If we hit a scope opener, the statement has ended
3063
                    // without finding anything, so it's probably an array
3064
                    // using PHP 7.1 short list syntax.
3065
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
840✔
3066
                        $isShortArray = true;
172✔
3067
                        break;
172✔
3068
                    }
3069

3070
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
840✔
3071
                        // Allow for control structures without braces.
3072
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
840✔
3073
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
840✔
3074
                            && isset(Tokens::SCOPE_OPENERS[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
82✔
3075
                            || isset($allowed[$this->tokens[$x]['code']]) === false
840✔
3076
                        ) {
3077
                            $isShortArray = true;
840✔
3078
                        }
3079

3080
                        break;
840✔
3081
                    }
3082
                }//end for
3083

3084
                if ($isShortArray === true) {
840✔
3085
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
840✔
3086
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
840✔
3087

3088
                    $closer = $this->tokens[$i]['bracket_closer'];
840✔
3089
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
840✔
3090
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
840✔
3091
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
840✔
3092
                        $line = $this->tokens[$i]['line'];
×
3093
                        StatusWriter::write("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
3094
                        $line = $this->tokens[$closer]['line'];
×
3095
                        StatusWriter::write("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
3096
                    }
3097
                }
3098

3099
                continue;
840✔
3100
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,296✔
3101
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
546✔
3102
                    // Not a match expression after all.
3103
                    $this->tokens[$i]['code'] = T_STRING;
68✔
3104
                    $this->tokens[$i]['type'] = 'T_STRING';
68✔
3105

3106
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
68✔
3107
                        StatusWriter::write("* token $i changed from T_MATCH to T_STRING", 2);
×
3108
                    }
3109

3110
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
68✔
3111
                        $opener = $this->tokens[$i]['parenthesis_opener'];
68✔
3112
                        $closer = $this->tokens[$i]['parenthesis_closer'];
68✔
3113
                        unset(
68✔
3114
                            $this->tokens[$opener]['parenthesis_owner'],
68✔
3115
                            $this->tokens[$closer]['parenthesis_owner']
68✔
3116
                        );
68✔
3117
                        unset(
68✔
3118
                            $this->tokens[$i]['parenthesis_opener'],
68✔
3119
                            $this->tokens[$i]['parenthesis_closer'],
68✔
3120
                            $this->tokens[$i]['parenthesis_owner']
68✔
3121
                        );
68✔
3122

3123
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
68✔
3124
                            StatusWriter::write("* cleaned parenthesis of token $i *", 2);
×
3125
                        }
3126
                    }
3127
                } else {
3128
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3129
                    $searchFor  = [
546✔
3130
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
546✔
3131
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
546✔
3132
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
546✔
3133
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
546✔
3134
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
546✔
3135
                    ];
546✔
3136
                    $searchFor += Tokens::SCOPE_OPENERS;
546✔
3137

3138
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
546✔
3139
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
546✔
3140
                            continue;
546✔
3141
                        }
3142

3143
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
328✔
3144
                            $x = $this->tokens[$x]['scope_closer'];
194✔
3145
                            continue;
194✔
3146
                        }
3147

3148
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
328✔
3149
                            $x = $this->tokens[$x]['parenthesis_closer'];
328✔
3150
                            continue;
328✔
3151
                        }
3152

3153
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
328✔
3154
                            $x = $this->tokens[$x]['bracket_closer'];
194✔
3155
                            continue;
194✔
3156
                        }
3157

3158
                        // This must be a double arrow, but make sure anyhow.
3159
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
328✔
3160
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
328✔
3161
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
328✔
3162

3163
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
328✔
3164
                                StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
3165
                            }
3166
                        }
3167
                    }//end for
3168
                }//end if
3169

3170
                continue;
546✔
3171
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,296✔
3172
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,296✔
3173
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,296✔
3174
            ) {
3175
                if ($lastSeenTypeToken < $i) {
1,294✔
3176
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3177
                    // No need to do it again.
3178
                    continue;
622✔
3179
                }
3180

3181
                /*
3182
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3183
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3184
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3185

3186
                    All type related tokens will be converted in one go as soon as this section is hit.
3187
                */
3188

3189
                $allowed  = Tokens::NAME_TOKENS;
1,294✔
3190
                $allowed += [
1,294✔
3191
                    T_CALLABLE => T_CALLABLE,
1,294✔
3192
                    T_SELF     => T_SELF,
1,294✔
3193
                    T_PARENT   => T_PARENT,
1,294✔
3194
                    T_STATIC   => T_STATIC,
1,294✔
3195
                    T_FALSE    => T_FALSE,
1,294✔
3196
                    T_TRUE     => T_TRUE,
1,294✔
3197
                    T_NULL     => T_NULL,
1,294✔
3198
                ];
1,294✔
3199

3200
                $suspectedType       = null;
1,294✔
3201
                $typeTokenCountAfter = 0;
1,294✔
3202

3203
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,294✔
3204
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
1,294✔
3205
                        continue;
1,294✔
3206
                    }
3207

3208
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,294✔
3209
                        ++$typeTokenCountAfter;
992✔
3210
                        continue;
992✔
3211
                    }
3212

3213
                    if (($typeTokenCountAfter > 0
1,294✔
3214
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,294✔
3215
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,294✔
3216
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,294✔
3217
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,294✔
3218
                    ) {
3219
                        // Skip past reference and variadic indicators for parameter types.
3220
                        continue;
664✔
3221
                    }
3222

3223
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,294✔
3224
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3225
                        $suspectedType = 'property or parameter';
874✔
3226
                        break;
874✔
3227
                    }
3228

3229
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,294✔
3230
                        // Possible arrow function.
3231
                        $suspectedType = 'return';
822✔
3232
                        break;
822✔
3233
                    }
3234

3235
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,294✔
3236
                        // Possible abstract method or interface method.
3237
                        $suspectedType = 'return';
1,168✔
3238
                        break;
1,168✔
3239
                    }
3240

3241
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,294✔
3242
                        && isset($this->tokens[$x]['scope_condition']) === true
1,294✔
3243
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,294✔
3244
                    ) {
3245
                        $suspectedType = 'return';
1,086✔
3246
                        break;
1,086✔
3247
                    }
3248

3249
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,218✔
3250
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3251
                        $suspectedType = 'constant';
762✔
3252
                        break;
762✔
3253
                    }
3254

3255
                    break;
1,218✔
3256
                }//end for
3257

3258
                if (($typeTokenCountAfter === 0
1,294✔
3259
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,294✔
3260
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,294✔
3261
                    || isset($suspectedType) === false
1,294✔
3262
                ) {
3263
                    // Definitely not a union, intersection or DNF type, move on.
3264
                    continue;
1,294✔
3265
                }
3266

3267
                if ($suspectedType === 'property or parameter') {
1,268✔
3268
                    unset($allowed[T_STATIC]);
874✔
3269
                }
3270

3271
                $typeTokenCountBefore = 0;
1,268✔
3272
                $typeOperators        = [$i];
1,268✔
3273
                $parenthesesCount     = 0;
1,268✔
3274
                $confirmed            = false;
1,268✔
3275
                $maybeNullable        = null;
1,268✔
3276

3277
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,268✔
3278
                    ++$parenthesesCount;
1,268✔
3279
                }
3280

3281
                for ($x = ($i - 1); $x >= 0; $x--) {
1,268✔
3282
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
1,268✔
3283
                        continue;
1,060✔
3284
                    }
3285

3286
                    if ($suspectedType === 'property or parameter'
1,268✔
3287
                        && $this->tokens[$x]['code'] === T_STRING
1,268✔
3288
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,268✔
3289
                    ) {
3290
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3291
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3292
                        $this->tokens[$x]['code'] = T_STATIC;
218✔
3293
                        $this->tokens[$x]['type'] = 'T_STATIC';
218✔
3294

3295
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
218✔
3296
                            $line = $this->tokens[$x]['line'];
×
3297
                            StatusWriter::write("* token $x on line $line changed back from T_STRING to T_STATIC", 1);
×
3298
                        }
3299
                    }
3300

3301
                    if ($suspectedType === 'property or parameter'
1,268✔
3302
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,268✔
3303
                    ) {
3304
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3305
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3306
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
874✔
3307
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
874✔
3308
                        ) {
3309
                            $confirmed = true;
798✔
3310
                            break;
798✔
3311
                        } else {
3312
                            // This may still be an arrow function which hasn't been handled yet.
3313
                            for ($y = ($x - 1); $y > 0; $y--) {
874✔
3314
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false
874✔
3315
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
874✔
3316
                                ) {
3317
                                    // Non-whitespace content.
3318
                                    break;
874✔
3319
                                }
3320
                            }
3321

3322
                            if ($this->tokens[$y]['code'] === T_FN) {
874✔
3323
                                $confirmed = true;
664✔
3324
                                break;
664✔
3325
                            }
3326
                        }
3327
                    }//end if
3328

3329
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,268✔
3330
                        ++$typeTokenCountBefore;
1,060✔
3331
                        continue;
1,060✔
3332
                    }
3333

3334
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3335
                    if (($typeTokenCountBefore > 0
1,268✔
3336
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,268✔
3337
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,268✔
3338
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,268✔
3339
                    ) {
3340
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
648✔
3341
                            $maybeNullable = $x;
218✔
3342
                        }
3343

3344
                        continue;
648✔
3345
                    }
3346

3347
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,268✔
3348
                        $typeOperators[] = $x;
916✔
3349
                        continue;
916✔
3350
                    }
3351

3352
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,268✔
3353
                        ++$parenthesesCount;
1,060✔
3354
                        $typeOperators[] = $x;
1,060✔
3355
                        continue;
1,060✔
3356
                    }
3357

3358
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,268✔
3359
                        // Make sure this is the colon for a return type.
3360
                        for ($y = ($x - 1); $y > 0; $y--) {
688✔
3361
                            if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false) {
688✔
3362
                                break;
688✔
3363
                            }
3364
                        }
3365

3366
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
688✔
3367
                            // Definitely not a union, intersection or DNF return type, move on.
3368
                            continue 2;
218✔
3369
                        }
3370

3371
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
688✔
3372
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
664✔
3373
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
218✔
3374
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
218✔
3375
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_USE
664✔
3376
                            ) {
3377
                                $confirmed = true;
664✔
3378
                            }
3379

3380
                            break;
664✔
3381
                        }
3382

3383
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3384
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
688✔
3385
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
664✔
3386
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$z]['code']]) === false) {
664✔
3387
                                    break;
664✔
3388
                                }
3389
                            }
3390

3391
                            if ($this->tokens[$z]['code'] === T_FN) {
664✔
3392
                                $confirmed = true;
664✔
3393
                            }
3394
                        }
3395

3396
                        break;
688✔
3397
                    }//end if
3398

3399
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,268✔
3400
                        $confirmed = true;
672✔
3401
                        break;
672✔
3402
                    }
3403

3404
                    if ($suspectedType === 'property or parameter'
1,268✔
3405
                        && (isset(Tokens::SCOPE_MODIFIERS[$this->tokens[$x]['code']]) === true
1,268✔
3406
                        || $this->tokens[$x]['code'] === T_VAR
1,268✔
3407
                        || $this->tokens[$x]['code'] === T_STATIC
1,268✔
3408
                        || $this->tokens[$x]['code'] === T_READONLY
1,268✔
3409
                        || $this->tokens[$x]['code'] === T_FINAL
1,268✔
3410
                        || $this->tokens[$x]['code'] === T_ABSTRACT)
1,268✔
3411
                    ) {
3412
                        // This will also confirm constructor property promotion parameters, but that's fine.
3413
                        $confirmed = true;
740✔
3414
                    }
3415

3416
                    break;
1,268✔
3417
                }//end for
3418

3419
                // Remember the last token we examined as part of the (non-)"type declaration".
3420
                $lastSeenTypeToken = $x;
1,268✔
3421

3422
                if ($confirmed === false
1,268✔
3423
                    && $suspectedType === 'property or parameter'
1,268✔
3424
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,268✔
3425
                ) {
3426
                    $parens = $this->tokens[$i]['nested_parenthesis'];
530✔
3427
                    $last   = end($parens);
530✔
3428

3429
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
530✔
3430
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
530✔
3431
                    ) {
3432
                        $confirmed = true;
530✔
3433
                    } else {
3434
                        // No parenthesis owner set, this may be an arrow function which has not yet
3435
                        // had additional processing done.
3436
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
162✔
3437
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
162✔
3438
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
162✔
3439
                                    continue;
162✔
3440
                                }
3441

3442
                                break;
162✔
3443
                            }
3444

3445
                            if ($this->tokens[$x]['code'] === T_FN) {
162✔
3446
                                for (--$x; $x >= 0; $x--) {
×
3447
                                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true
×
3448
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3449
                                    ) {
3450
                                        continue;
×
3451
                                    }
3452

3453
                                    break;
×
3454
                                }
3455

3456
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3457
                                    $confirmed = true;
×
3458
                                }
3459
                            }
3460
                        }//end if
3461
                    }//end if
3462

3463
                    unset($parens, $last);
530✔
3464
                }//end if
3465

3466
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,268✔
3467
                    // Not a (valid) union, intersection or DNF type after all, move on.
3468
                    continue;
1,192✔
3469
                }
3470

3471
                foreach ($typeOperators as $x) {
968✔
3472
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
968✔
3473
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
892✔
3474
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
892✔
3475

3476
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
892✔
3477
                            $line = $this->tokens[$x]['line'];
×
3478
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
×
3479
                        }
3480
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
968✔
3481
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
892✔
3482
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
892✔
3483

3484
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
892✔
3485
                            $line = $this->tokens[$x]['line'];
×
3486
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
×
3487
                        }
3488
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
968✔
3489
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
968✔
3490
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
968✔
3491

3492
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
968✔
3493
                            $line = $this->tokens[$x]['line'];
×
3494
                            StatusWriter::write("* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS", 1);
×
3495
                        }
3496
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
968✔
3497
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
968✔
3498
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
968✔
3499

3500
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
968✔
3501
                            $line = $this->tokens[$x]['line'];
×
3502
                            StatusWriter::write("* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS", 1);
×
3503
                        }
3504
                    }//end if
3505
                }//end foreach
3506

3507
                if (isset($maybeNullable) === true) {
968✔
3508
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
218✔
3509
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
218✔
3510

3511
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
218✔
3512
                        $line = $this->tokens[$maybeNullable]['line'];
×
3513
                        StatusWriter::write("* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE", 1);
×
3514
                    }
3515
                }
3516

3517
                continue;
968✔
3518
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,296✔
3519
                || $this->tokens[$i]['code'] === T_FALSE
1,296✔
3520
                || $this->tokens[$i]['code'] === T_NULL
1,296✔
3521
            ) {
3522
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,164✔
3523
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
1,164✔
3524
                        // Non-whitespace content.
3525
                        break;
1,164✔
3526
                    }
3527
                }
3528

3529
                if ($x !== $numTokens
1,164✔
3530
                    && isset(static::T_STRING_CONTEXTS[$this->tokens[$x]['code']]) === true
1,164✔
3531
                ) {
3532
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3533
                        $line = $this->tokens[$i]['line'];
×
3534
                        $type = $this->tokens[$i]['type'];
×
3535
                        StatusWriter::write("* token $i on line $line changed from $type to T_STRING", 1);
×
3536
                    }
3537

3538
                    $this->tokens[$i]['code'] = T_STRING;
×
3539
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3540
                }
3541
            }//end if
3542

3543
            if (($this->tokens[$i]['code'] !== T_CASE
1,296✔
3544
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,296✔
3545
                || isset($this->tokens[$i]['scope_opener']) === false
1,296✔
3546
            ) {
3547
                // Only interested in CASE and DEFAULT statements from here on in.
3548
                continue;
1,296✔
3549
            }
3550

3551
            $scopeOpener = $this->tokens[$i]['scope_opener'];
322✔
3552
            $scopeCloser = $this->tokens[$i]['scope_closer'];
322✔
3553

3554
            // If the first char after the opener is a curly brace
3555
            // and that brace has been ignored, it is actually
3556
            // opening this case statement and the opener and closer are
3557
            // probably set incorrectly.
3558
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
322✔
3559
                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
322✔
3560
                    // Non-whitespace content.
3561
                    break;
322✔
3562
                }
3563
            }
3564

3565
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
322✔
3566
                // Special case for multiple CASE statements that share the same
3567
                // closer. Because we are going backwards through the file, this next
3568
                // CASE statement is already fixed, so just use its closer and don't
3569
                // worry about fixing anything.
3570
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3571
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3572
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3573
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3574
                    $newType = $this->tokens[$newCloser]['type'];
×
3575
                    $line    = $this->tokens[$i]['line'];
×
3576
                    StatusWriter::write("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3577
                }
3578

3579
                continue;
×
3580
            }
3581

3582
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
322✔
3583
                || isset($this->tokens[$x]['scope_condition']) === true
322✔
3584
            ) {
3585
                // Not a CASE/DEFAULT with a curly brace opener.
3586
                continue;
322✔
3587
            }
3588

3589
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3590
            // not whatever it already is. The opener needs to be the opening curly
3591
            // brace so everything matches up.
3592
            $newCloser = $this->tokens[$x]['bracket_closer'];
36✔
3593
            foreach ([$i, $x, $newCloser] as $index) {
36✔
3594
                $this->tokens[$index]['scope_condition'] = $i;
36✔
3595
                $this->tokens[$index]['scope_opener']    = $x;
36✔
3596
                $this->tokens[$index]['scope_closer']    = $newCloser;
36✔
3597
            }
3598

3599
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
36✔
3600
                $line      = $this->tokens[$i]['line'];
×
3601
                $tokenType = $this->tokens[$i]['type'];
×
3602

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

3607
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3608
                $newType = $this->tokens[$newCloser]['type'];
×
3609
                StatusWriter::write("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3610
            }
3611

3612
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
36✔
3613
                unset($this->tokens[$scopeOpener]['scope_condition']);
36✔
3614
                unset($this->tokens[$scopeOpener]['scope_opener']);
36✔
3615
                unset($this->tokens[$scopeOpener]['scope_closer']);
36✔
3616
            }
3617

3618
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
36✔
3619
                unset($this->tokens[$scopeCloser]['scope_condition']);
36✔
3620
                unset($this->tokens[$scopeCloser]['scope_opener']);
36✔
3621
                unset($this->tokens[$scopeCloser]['scope_closer']);
36✔
3622
            } else {
3623
                // We were using a shared closer. All tokens that were
3624
                // sharing this closer with us, except for the scope condition
3625
                // and it's opener, need to now point to the new closer.
3626
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3627
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3628
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3629
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3630
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3631
                    ) {
3632
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3633

3634
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3635
                            $line      = $this->tokens[$y]['line'];
×
3636
                            $tokenType = $this->tokens[$y]['type'];
×
3637
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3638
                            $newType   = $this->tokens[$newCloser]['type'];
×
3639
                            StatusWriter::write("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3640
                        }
3641
                    }
3642
                }
3643
            }//end if
3644

3645
            unset($this->tokens[$x]['bracket_opener']);
36✔
3646
            unset($this->tokens[$x]['bracket_closer']);
36✔
3647
            unset($this->tokens[$newCloser]['bracket_opener']);
36✔
3648
            unset($this->tokens[$newCloser]['bracket_closer']);
36✔
3649
            $this->tokens[$scopeCloser]['conditions'][] = $i;
36✔
3650

3651
            // Now fix up all the tokens that think they are
3652
            // inside the CASE/DEFAULT statement when they are really outside.
3653
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
36✔
3654
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3655
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3656
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3657
                        unset($this->tokens[$x]['conditions'][$num]);
×
3658

3659
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3660
                            $type     = $this->tokens[$x]['type'];
×
3661
                            $oldConds = '';
×
3662
                            foreach ($oldConditions as $condition) {
×
3663
                                $oldConds .= Tokens::tokenName($condition).',';
×
3664
                            }
3665

3666
                            $oldConds = rtrim($oldConds, ',');
×
3667

3668
                            $newConds = '';
×
3669
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3670
                                $newConds .= Tokens::tokenName($condition).',';
×
3671
                            }
3672

3673
                            $newConds = rtrim($newConds, ',');
×
3674

3675
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
3676
                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", 3);
×
3677
                        }
3678

3679
                        break;
×
3680
                    }//end if
3681
                }//end foreach
3682
            }//end for
3683
        }//end for
3684

3685
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,296✔
3686
            StatusWriter::write('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3687
        }
3688

3689
    }//end processAdditional()
3690

3691

3692
    /**
3693
     * Takes a token produced from <code>token_get_all()</code> and produces a
3694
     * more uniform token.
3695
     *
3696
     * @param string|array $token The token to convert.
3697
     *
3698
     * @return array The new token.
3699
     */
3700
    public static function standardiseToken($token)
×
3701
    {
3702
        if (isset($token[1]) === false) {
×
3703
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
×
3704
                return self::$resolveTokenCache[$token[0]];
×
3705
            }
3706
        } else {
3707
            $cacheKey = null;
×
3708
            if ($token[0] === T_STRING) {
×
3709
                $cacheKey = strtolower($token[1]);
×
3710
            } else if ($token[0] !== T_CURLY_OPEN) {
×
3711
                $cacheKey = $token[0];
×
3712
            }
3713

3714
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
×
3715
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3716
                $newToken['content'] = $token[1];
×
3717
                return $newToken;
×
3718
            }
3719
        }
3720

3721
        if (isset($token[1]) === false) {
×
3722
            return self::resolveSimpleToken($token[0]);
×
3723
        }
3724

3725
        if ($token[0] === T_STRING) {
×
3726
            switch ($cacheKey) {
3727
            case 'false':
×
3728
                $newToken['type'] = 'T_FALSE';
×
3729
                break;
×
3730
            case 'true':
×
3731
                $newToken['type'] = 'T_TRUE';
×
3732
                break;
×
3733
            case 'null':
×
3734
                $newToken['type'] = 'T_NULL';
×
3735
                break;
×
3736
            case 'self':
×
3737
                $newToken['type'] = 'T_SELF';
×
3738
                break;
×
3739
            case 'parent':
×
3740
                $newToken['type'] = 'T_PARENT';
×
3741
                break;
×
3742
            default:
3743
                $newToken['type'] = 'T_STRING';
×
3744
                break;
×
3745
            }
3746

3747
            $newToken['code'] = constant($newToken['type']);
×
3748

3749
            self::$resolveTokenCache[$cacheKey] = $newToken;
×
3750
        } else if ($token[0] === T_CURLY_OPEN) {
×
3751
            $newToken = [
×
3752
                'code' => T_OPEN_CURLY_BRACKET,
×
3753
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3754
            ];
×
3755
        } else {
3756
            $newToken = [
×
3757
                'code' => $token[0],
×
3758
                'type' => Tokens::tokenName($token[0]),
×
3759
            ];
×
3760

3761
            self::$resolveTokenCache[$token[0]] = $newToken;
×
3762
        }//end if
3763

3764
        $newToken['content'] = $token[1];
×
3765
        return $newToken;
×
3766

3767
    }//end standardiseToken()
3768

3769

3770
    /**
3771
     * Converts simple tokens into a format that conforms to complex tokens
3772
     * produced by token_get_all().
3773
     *
3774
     * Simple tokens are tokens that are not in array form when produced from
3775
     * token_get_all().
3776
     *
3777
     * @param string $token The simple token to convert.
3778
     *
3779
     * @return array The new token in array format.
3780
     */
3781
    public static function resolveSimpleToken($token)
×
3782
    {
3783
        $newToken = [];
×
3784

3785
        switch ($token) {
3786
        case '{':
×
3787
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
×
3788
            break;
×
3789
        case '}':
×
3790
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
×
3791
            break;
×
3792
        case '[':
×
3793
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
×
3794
            break;
×
3795
        case ']':
×
3796
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
×
3797
            break;
×
3798
        case '(':
×
3799
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
×
3800
            break;
×
3801
        case ')':
×
3802
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
×
3803
            break;
×
3804
        case ':':
×
3805
            $newToken['type'] = 'T_COLON';
×
3806
            break;
×
3807
        case '.':
×
3808
            $newToken['type'] = 'T_STRING_CONCAT';
×
3809
            break;
×
3810
        case ';':
×
3811
            $newToken['type'] = 'T_SEMICOLON';
×
3812
            break;
×
3813
        case '=':
×
3814
            $newToken['type'] = 'T_EQUAL';
×
3815
            break;
×
3816
        case '*':
×
3817
            $newToken['type'] = 'T_MULTIPLY';
×
3818
            break;
×
3819
        case '/':
×
3820
            $newToken['type'] = 'T_DIVIDE';
×
3821
            break;
×
3822
        case '+':
×
3823
            $newToken['type'] = 'T_PLUS';
×
3824
            break;
×
3825
        case '-':
×
3826
            $newToken['type'] = 'T_MINUS';
×
3827
            break;
×
3828
        case '%':
×
3829
            $newToken['type'] = 'T_MODULUS';
×
3830
            break;
×
3831
        case '^':
×
3832
            $newToken['type'] = 'T_BITWISE_XOR';
×
3833
            break;
×
3834
        case '&':
×
3835
            $newToken['type'] = 'T_BITWISE_AND';
×
3836
            break;
×
3837
        case '|':
×
3838
            $newToken['type'] = 'T_BITWISE_OR';
×
3839
            break;
×
3840
        case '~':
×
3841
            $newToken['type'] = 'T_BITWISE_NOT';
×
3842
            break;
×
3843
        case '<':
×
3844
            $newToken['type'] = 'T_LESS_THAN';
×
3845
            break;
×
3846
        case '>':
×
3847
            $newToken['type'] = 'T_GREATER_THAN';
×
3848
            break;
×
3849
        case '!':
×
3850
            $newToken['type'] = 'T_BOOLEAN_NOT';
×
3851
            break;
×
3852
        case ',':
×
3853
            $newToken['type'] = 'T_COMMA';
×
3854
            break;
×
3855
        case '@':
×
3856
            $newToken['type'] = 'T_ASPERAND';
×
3857
            break;
×
3858
        case '$':
×
3859
            $newToken['type'] = 'T_DOLLAR';
×
3860
            break;
×
3861
        case '`':
×
3862
            $newToken['type'] = 'T_BACKTICK';
×
3863
            break;
×
3864
        default:
3865
            $newToken['type'] = 'T_NONE';
×
3866
            break;
×
3867
        }//end switch
3868

3869
        $newToken['code']    = constant($newToken['type']);
×
3870
        $newToken['content'] = $token;
×
3871

3872
        self::$resolveTokenCache[$token] = $newToken;
×
3873
        return $newToken;
×
3874

3875
    }//end resolveSimpleToken()
3876

3877

3878
    /**
3879
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3880
     * Handle parenthesis balancing while searching for closing token
3881
     *
3882
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3883
     * @param int             $start        The starting position.
3884
     * @param string|string[] $openerTokens The opening character.
3885
     * @param string          $closerChar   The closing character.
3886
     *
3887
     * @return int|null The position of the closing token, if found. NULL otherwise.
3888
     */
3889
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
34✔
3890
    {
3891
        $numTokens    = count($tokens);
34✔
3892
        $stack        = [0];
34✔
3893
        $closer       = null;
34✔
3894
        $openerTokens = (array) $openerTokens;
34✔
3895

3896
        for ($x = $start; $x < $numTokens; $x++) {
34✔
3897
            if (in_array($tokens[$x], $openerTokens, true) === true
34✔
3898
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
34✔
3899
            ) {
3900
                $stack[] = $x;
34✔
3901
            } else if ($tokens[$x] === $closerChar) {
34✔
3902
                array_pop($stack);
34✔
3903
                if (empty($stack) === true) {
34✔
3904
                    $closer = $x;
34✔
3905
                    break;
34✔
3906
                }
3907
            }
3908
        }
3909

3910
        return $closer;
34✔
3911

3912
    }//end findCloser()
3913

3914

3915
    /**
3916
     * PHP 8 attributes parser for PHP < 8
3917
     * Handles single-line and multiline attributes.
3918
     *
3919
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3920
     * @param int   $stackPtr The current position in token array.
3921
     *
3922
     * @return array|null The array of parsed attribute tokens
3923
     */
3924
    private function parsePhpAttribute(array &$tokens, $stackPtr)
×
3925
    {
3926

3927
        $token = $tokens[$stackPtr];
×
3928

3929
        $commentBody = substr($token[1], 2);
×
3930
        $subTokens   = @token_get_all('<?php '.$commentBody);
×
3931

3932
        foreach ($subTokens as $i => $subToken) {
×
3933
            if (is_array($subToken) === true
×
3934
                && $subToken[0] === T_COMMENT
×
3935
                && strpos($subToken[1], '#[') === 0
×
3936
            ) {
3937
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
×
3938
                if ($reparsed !== null) {
×
3939
                    array_splice($subTokens, $i, 1, $reparsed);
×
3940
                } else {
3941
                    $subToken[0] = T_ATTRIBUTE;
×
3942
                }
3943
            }
3944
        }
3945

3946
        array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
×
3947

3948
        // Go looking for the close bracket.
3949
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
×
3950
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
×
3951
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
×
3952
                if (is_array($token) === true) {
×
3953
                    $commentBody .= $token[1];
×
3954
                } else {
3955
                    $commentBody .= $token;
×
3956
                }
3957
            }
3958

3959
            $subTokens = @token_get_all('<?php '.$commentBody);
×
3960
            array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
×
3961

3962
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
×
3963
            if ($bracketCloser !== null) {
×
3964
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
×
3965
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
×
3966
            }
3967
        }
3968

3969
        if ($bracketCloser === null) {
×
3970
            return null;
×
3971
        }
3972

3973
        return $subTokens;
×
3974

3975
    }//end parsePhpAttribute()
3976

3977

3978
    /**
3979
     * Creates a map for the attributes tokens that surround other tokens.
3980
     *
3981
     * @return void
3982
     */
3983
    private function createAttributesNestingMap()
2✔
3984
    {
3985
        $map = [];
2✔
3986
        for ($i = 0; $i < $this->numTokens; $i++) {
2✔
3987
            if (isset($this->tokens[$i]['attribute_opener']) === true
2✔
3988
                && $i === $this->tokens[$i]['attribute_opener']
2✔
3989
            ) {
3990
                if (empty($map) === false) {
2✔
3991
                    $this->tokens[$i]['nested_attributes'] = $map;
2✔
3992
                }
3993

3994
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
2✔
3995
                    $map[$this->tokens[$i]['attribute_opener']]
2✔
3996
                        = $this->tokens[$i]['attribute_closer'];
2✔
3997
                }
3998
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
2✔
3999
                && $i === $this->tokens[$i]['attribute_closer']
2✔
4000
            ) {
4001
                array_pop($map);
2✔
4002
                if (empty($map) === false) {
2✔
4003
                    $this->tokens[$i]['nested_attributes'] = $map;
2✔
4004
                }
4005
            } else {
4006
                if (empty($map) === false) {
2✔
4007
                    $this->tokens[$i]['nested_attributes'] = $map;
2✔
4008
                }
4009
            }//end if
4010
        }//end for
4011

4012
    }//end createAttributesNestingMap()
4013

4014

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