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

PHPCSStandards / PHP_CodeSniffer / 17663829912

12 Sep 2025 03:38AM UTC coverage: 78.786%. Remained the same
17663829912

Pull #1245

github

web-flow
Merge ccdabfebd into 607b279a1
Pull Request #1245: CS: normalize code style rules [7]

677 of 1022 new or added lines in 17 files covered. (66.24%)

7 existing lines in 1 file now uncovered.

19732 of 25045 relevant lines covered (78.79%)

96.47 hits per line

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

86.74
/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 $code The code to tokenize.
535
     *
536
     * @return array
537
     */
538
    protected function tokenize(string $code)
3,615✔
539
    {
540
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,615✔
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($code);
3,615✔
549
        $finalTokens = [];
3,615✔
550

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

555
        $insideInlineIf         = [];
3,615✔
556
        $insideUseGroup         = false;
3,615✔
557
        $insideConstDeclaration = false;
3,615✔
558

559
        $commentTokenizer = new Comment();
3,615✔
560

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

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

570
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,615✔
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
            }
590

591
            if ($newStackPtr > 0
3,615✔
592
                && isset(Tokens::EMPTY_TOKENS[$finalTokens[($newStackPtr - 1)]['code']]) === false
3,615✔
593
            ) {
594
                $lastNotEmptyToken = ($newStackPtr - 1);
3,615✔
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") {
3,615✔
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
            }
627

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

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

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

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

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

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

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

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

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

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

692
                            break;
132✔
693
                        }
694
                    }
695
                }
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
2,819✔
699
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,819✔
700
                    || $insideConstDeclaration === true
2,819✔
701
                ) {
702
                    $preserveKeyword = true;
2,462✔
703

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

712
                        break;
2,462✔
713
                    }
714

715
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
2,462✔
716
                        $preserveKeyword        = false;
920✔
717
                        $insideConstDeclaration = false;
920✔
718
                    }
719
                }
720

721
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,819✔
722
                    $preserveKeyword = true;
639✔
723

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

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

733
                        break;
639✔
734
                    }
735
                }
736

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

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

749
                    $newStackPtr++;
1,423✔
750
                    continue;
1,423✔
751
                }
752
            }
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) {
3,615✔
760
                $insideConstDeclaration = true;
2,655✔
761
            }
762

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

767
            if ($insideConstDeclaration === true
3,615✔
768
                && $tokenIsArray === false
3,615✔
769
                && ($token[0] === '=' || $token[0] === ';')
3,615✔
770
            ) {
771
                $insideConstDeclaration = false;
855✔
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
3,615✔
782
                && $token[0] === T_STATIC
3,615✔
783
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
3,615✔
784
            ) {
785
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,091✔
786
                    if (is_array($tokens[$i]) === true
2,091✔
787
                        && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
2,091✔
788
                    ) {
789
                        continue;
1,443✔
790
                    }
791

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

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

803
                    break;
2,091✔
804
                }
805
            }
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
3,615✔
814
                && $tokenIsArray === true
3,615✔
815
                // PHP < 7.4 with short open tags off.
816
                && (($stackPtr === ($numTokens - 1)
3,615✔
817
                && $token[0] === T_INLINE_HTML
3,615✔
818
                && stripos($token[1], '<?php') === 0)
2,433✔
819
                // PHP < 7.4 with short open tags on.
2,412✔
820
                || ($stackPtr === ($numTokens - 2)
3,615✔
821
                && $token[0] === T_OPEN_TAG
3,615✔
822
                && $token[1] === '<?'
3,615✔
823
                && is_array($tokens[($stackPtr + 1)]) === true
3,615✔
824
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,615✔
825
                && strtolower($tokens[($stackPtr + 1)][1]) === 'php'))
3,615✔
826
            ) {
827
                if ($token[0] === T_INLINE_HTML) {
2✔
828
                    $finalTokens[$newStackPtr] = [
2✔
829
                        'code'    => T_OPEN_TAG,
2✔
830
                        'type'    => 'T_OPEN_TAG',
2✔
831
                        'content' => $token[1],
2✔
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++;
2✔
844
                continue;
2✔
845
            }
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
3,615✔
853
                && $token[0] === T_OPEN_TAG
3,615✔
854
                && stripos($token[1], '<?php') === 0
3,615✔
855
            ) {
856
                $openTagAndWhiteSpace = str_split($token[1], 5);
3,615✔
857

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

865
                if (isset($openTagAndWhiteSpace[1]) === true) {
3,615✔
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
3,615✔
871
                        && $openTagAndWhiteSpace[1] === ' '
3,615✔
872
                        && is_array($tokens[($stackPtr + 1)]) === true
3,615✔
873
                        && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,615✔
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];
63✔
878

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

889
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,615✔
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++;
3,615✔
894
                    }
895
                }
896

897
                continue;
3,615✔
898
            }
899

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

904
            if ($tokenIsArray === true
3,615✔
905
                && ($token[0] === T_DOC_COMMENT
3,615✔
906
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
3,615✔
907
            ) {
908
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
93✔
909
                $finalTokens  += $commentTokens;
93✔
910
                $newStackPtr  += count($commentTokens);
93✔
911
                continue;
93✔
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
3,615✔
919
                && $tokenIsArray === true
3,615✔
920
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
3,615✔
921
                && isset($tokens[($stackPtr + 1)]) === true
3,615✔
922
                && is_array($tokens[($stackPtr + 1)]) === true
3,615✔
923
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,615✔
924
            ) {
925
                $nextToken = $tokens[($stackPtr + 1)];
1,858✔
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,858✔
930
                    $token[1] .= $nextToken[1];
1,542✔
931
                    $tokens[($stackPtr + 1)] = null;
1,542✔
932

933
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,542✔
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,506✔
939
                        $token[1] .= "\r\n";
30✔
940
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
30✔
941
                    } elseif (strpos($nextToken[1], "\n\r") === 0) {
1,476✔
942
                        $token[1] .= "\n\r";
×
943
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
944
                    } elseif (strpos($nextToken[1], "\n") === 0) {
1,476✔
945
                        $token[1] .= "\n";
1,476✔
946
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
1,476✔
947
                    }
948

949
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,506✔
950
                        StatusWriter::write("* stripped first newline after comment and added it to comment token $stackPtr", 2);
×
951
                    }
952
                }
953
            }
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
3,615✔
962
                && $tokenIsArray === true && $token[1] === '0'
3,615✔
963
                && (isset($tokens[($stackPtr + 1)]) === true
2,993✔
964
                && is_array($tokens[($stackPtr + 1)]) === true
2,993✔
965
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,993✔
966
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,993✔
967
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,993✔
968
                && $tokens[($stackPtr + 1)][1][1] !== '_')
3,615✔
969
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
3,615✔
970
            ) {
971
                $finalTokens[$newStackPtr] = [
37✔
972
                    'code'    => T_LNUMBER,
37✔
973
                    'type'    => 'T_LNUMBER',
37✔
974
                    'content' => $token[1] .= $matches[1],
37✔
975
                ];
976
                $newStackPtr++;
37✔
977

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

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

992
                $stackPtr++;
37✔
993
                continue;
37✔
994
            }
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
3,615✔
1003
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
3,615✔
1004
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
3,615✔
1005
            ) {
1006
                $finalTokens[$newStackPtr] = [
880✔
1007
                    'code'    => T_BITWISE_AND,
880✔
1008
                    'type'    => 'T_BITWISE_AND',
880✔
1009
                    'content' => $token[1],
880✔
1010
                ];
880✔
1011
                $newStackPtr++;
880✔
1012
                continue;
880✔
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"')) {
3,615✔
1023
                // Binary casts need a special token.
1024
                if ($token[0] === 'b"') {
150✔
1025
                    $finalTokens[$newStackPtr] = [
×
1026
                        'code'    => T_BINARY_CAST,
×
1027
                        'type'    => 'T_BINARY_CAST',
×
1028
                        'content' => 'b',
×
1029
                    ];
1030
                    $newStackPtr++;
×
1031
                }
1032

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

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

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

1063
                $stackPtr = $i;
150✔
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);
150✔
1068
                $numLines   = count($tokenLines);
150✔
1069
                $newToken   = [];
150✔
1070

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

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

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

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

1095
            if ($tokenIsArray === true
3,615✔
1096
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
3,615✔
1097
                && (substr($token[1], 0, 2) === 'b"'
3,415✔
1098
                || substr($token[1], 0, 2) === "b'")
3,615✔
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
3,615✔
1110
                && $token[0] === T_STRING_CAST
3,615✔
1111
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
3,615✔
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) {
3,615✔
1131
                // Add the start heredoc token to the final array.
1132
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
135✔
1133

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

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

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

1160
                if ($i === $numTokens) {
135✔
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) {
3✔
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;
3✔
1171
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
3✔
1172
                    $newStackPtr++;
3✔
1173
                    continue;
3✔
1174
                }
1175

1176
                $stackPtr = $i;
132✔
1177
                $newStackPtr++;
132✔
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);
132✔
1182
                $numLines   = count($tokenLines);
132✔
1183
                $newToken   = [];
132✔
1184

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

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

1203
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1204
                    $newStackPtr++;
132✔
1205
                }
1206

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

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

1215
                $newStackPtr++;
132✔
1216

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

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

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

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

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

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

1255
                    $newStackPtr++;
311✔
1256
                    continue;
311✔
1257
                }
1258
            }
1259

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

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

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

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

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

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

1290
                    if ($tokens[$i][0] === T_SWITCH) {
1,101✔
1291
                        break;
1,035✔
1292
                    }
1293

1294
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,101✔
1295
                        $isEnumCase = true;
132✔
1296
                        break;
132✔
1297
                    }
1298
                }
1299

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

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

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

1314
                    $newStackPtr++;
132✔
1315
                    continue;
132✔
1316
                }
1317
            }
1318

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

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

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

1351
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
197✔
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;
197✔
1358

1359
                continue;
197✔
1360
            }
1361

1362
            /*
1363
             * There is a select group of keywords - true, false, null, exit and die -,
1364
             * which can be used in fully qualified form, but should still be tokenized as the keyword.
1365
             */
1366

1367
            if (PHP_VERSION_ID >= 80000
3,615✔
1368
                && $tokenIsArray === true
3,615✔
1369
                && $token[0] === T_NAME_FULLY_QUALIFIED
3,615✔
1370
            ) {
1371
                $specialCasedType = null;
1,620✔
1372
                $contentLc        = strtolower($token[1]);
1,620✔
1373
                if ($contentLc === '\exit' || $contentLc === '\die') {
1,620✔
1374
                    $specialCasedType = 'T_EXIT';
990✔
1375
                } elseif ($contentLc === '\true') {
1,200✔
1376
                    $specialCasedType = 'T_TRUE';
470✔
1377
                } elseif ($contentLc === '\false') {
1,200✔
1378
                    $specialCasedType = 'T_FALSE';
530✔
1379
                } elseif ($contentLc === '\null') {
1,140✔
1380
                    $specialCasedType = 'T_NULL';
470✔
1381
                }
1382

1383
                if ($specialCasedType !== null) {
1,620✔
1384
                    $newToken            = [];
1,382✔
1385
                    $newToken['code']    = constant($specialCasedType);
1,382✔
1386
                    $newToken['type']    = $specialCasedType;
1,382✔
1387
                    $newToken['content'] = $token[1];
1,382✔
1388
                    $finalTokens[$newStackPtr] = $newToken;
1,382✔
1389
                    ++$newStackPtr;
1,382✔
1390

1391
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,382✔
1392
                        StatusWriter::write("* token $stackPtr retokenized to $specialCasedType ({$token[1]})", 2);
×
1393
                    }
1394

1395
                    continue;
1,382✔
1396
                }
1397
            }
1398

1399
            /*
1400
                Before PHP 8.0, namespaced names were not tokenized as a single token.
1401

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

1408
            if (PHP_VERSION_ID < 80000
3,615✔
1409
                && $tokenIsArray === true
3,615✔
1410
                && ($token[0] === T_STRING
3,615✔
1411
                || $token[0] === T_NAMESPACE
3,615✔
1412
                || ($token[0] === T_NS_SEPARATOR
3,615✔
1413
                && isset($tokens[($stackPtr + 1)]) === true
3,615✔
1414
                && is_array($tokens[($stackPtr + 1)]) === true
3,615✔
1415
                && isset(Tokens::EMPTY_TOKENS[$tokens[($stackPtr + 1)][0]]) === false
3,615✔
1416
                && preg_match(self::PHP_LABEL_REGEX, $tokens[($stackPtr + 1)][1]) === 1))
3,615✔
1417
            ) {
1418
                $nameStart = $stackPtr;
1,071✔
1419
                $i         = $stackPtr;
1,071✔
1420
                $newToken  = [];
1,071✔
1421
                $newToken['content'] = $token[1];
1,071✔
1422

1423
                switch ($token[0]) {
1,071✔
1424
                    case T_STRING:
1,071✔
1425
                        $newToken['code'] = T_NAME_QUALIFIED;
1,071✔
1426
                        $newToken['type'] = 'T_NAME_QUALIFIED';
1,071✔
1427
                        break;
1,071✔
1428
                    case T_NAMESPACE:
893✔
1429
                        $newToken['code'] = T_NAME_RELATIVE;
644✔
1430
                        $newToken['type'] = 'T_NAME_RELATIVE';
644✔
1431
                        break;
644✔
1432
                    case T_NS_SEPARATOR:
810✔
1433
                        $newToken['code'] = T_NAME_FULLY_QUALIFIED;
810✔
1434
                        $newToken['type'] = 'T_NAME_FULLY_QUALIFIED';
810✔
1435

1436
                        if (is_array($tokens[($i - 1)]) === true
810✔
1437
                            && isset(Tokens::EMPTY_TOKENS[$tokens[($i - 1)][0]]) === false
810✔
1438
                            && preg_match(self::PHP_LABEL_REGEX, $tokens[($i - 1)][1]) === 1
810✔
1439
                        ) {
1440
                            // The namespaced name starts with a reserved keyword. Move one token back.
1441
                            $newToken['code']    = T_NAME_QUALIFIED;
69✔
1442
                            $newToken['type']    = 'T_NAME_QUALIFIED';
69✔
1443
                            $newToken['content'] = $tokens[($i - 1)][1];
69✔
1444
                            --$nameStart;
69✔
1445
                            --$i;
69✔
1446
                            break;
69✔
1447
                        }
1448

1449
                        ++$i;
810✔
1450
                        $newToken['content'] .= $tokens[$i][1];
810✔
1451
                        break;
810✔
1452
                }
1453

1454
                while (isset($tokens[($i + 1)], $tokens[($i + 2)]) === true
1,071✔
1455
                    && is_array($tokens[($i + 1)]) === true && $tokens[($i + 1)][0] === T_NS_SEPARATOR
1,071✔
1456
                    && is_array($tokens[($i + 2)]) === true
1,071✔
1457
                    && isset(Tokens::EMPTY_TOKENS[$tokens[($i + 2)][0]]) === false
1,071✔
1458
                    && preg_match(self::PHP_LABEL_REGEX, $tokens[($i + 2)][1]) === 1
1,071✔
1459
                ) {
1460
                    $newToken['content'] .= $tokens[($i + 1)][1] . $tokens[($i + 2)][1];
553✔
1461
                    $i = ($i + 2);
553✔
1462
                }
1463

1464
                if ($i !== $nameStart) {
1,071✔
1465
                    if ($nameStart !== $stackPtr) {
893✔
1466
                        // This must be a qualified name starting with a reserved keyword.
1467
                        // We need to overwrite the previously set final token.
1468
                        --$newStackPtr;
69✔
1469
                    }
1470

1471
                    // Special case fully qualified true/false/null/exit/die.
1472
                    if ($newToken['code'] === T_NAME_FULLY_QUALIFIED) {
893✔
1473
                        $newContentLc = strtolower($newToken['content']);
810✔
1474
                        if ($newContentLc === '\exit' || $newContentLc === '\die') {
810✔
1475
                            $newToken['code'] = T_EXIT;
495✔
1476
                            $newToken['type'] = 'T_EXIT';
495✔
1477
                        } elseif ($newContentLc === '\true') {
600✔
1478
                            $newToken['code'] = T_TRUE;
235✔
1479
                            $newToken['type'] = 'T_TRUE';
235✔
1480
                        } elseif ($newContentLc === '\false') {
600✔
1481
                            $newToken['code'] = T_FALSE;
265✔
1482
                            $newToken['type'] = 'T_FALSE';
265✔
1483
                        } elseif ($newContentLc === '\null') {
570✔
1484
                            $newToken['code'] = T_NULL;
235✔
1485
                            $newToken['type'] = 'T_NULL';
235✔
1486
                        }
1487
                    }
1488

1489
                    $finalTokens[$newStackPtr] = $newToken;
893✔
1490
                    $newStackPtr++;
893✔
1491
                    $stackPtr = $i;
893✔
1492

1493
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
893✔
1494
                        $type    = $newToken['type'];
×
1495
                        $content = $newToken['content'];
×
1496
                        StatusWriter::write("* token $nameStart to $i ($content) retokenized to $type", 2);
×
1497
                    }
1498

1499
                    continue;
893✔
1500
                }
1501
            }
1502

1503
            /*
1504
                PHP 8.0 Attributes
1505
            */
1506

1507
            if (PHP_VERSION_ID < 80000
3,615✔
1508
                && $token[0] === T_COMMENT
3,615✔
1509
                && strpos($token[1], '#[') === 0
3,615✔
1510
            ) {
1511
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
17✔
1512
                if ($subTokens !== null) {
17✔
1513
                    array_splice($tokens, $stackPtr, 1, $subTokens);
17✔
1514
                    $numTokens = count($tokens);
17✔
1515

1516
                    $tokenIsArray = true;
17✔
1517
                    $token        = $tokens[$stackPtr];
17✔
1518
                } else {
1519
                    $token[0] = T_ATTRIBUTE;
17✔
1520
                }
1521
            }
1522

1523
            if ($tokenIsArray === true
3,615✔
1524
                && $token[0] === T_ATTRIBUTE
3,615✔
1525
            ) {
1526
                // Go looking for the close bracket.
1527
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1528

1529
                $newToken            = [];
51✔
1530
                $newToken['code']    = T_ATTRIBUTE;
51✔
1531
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1532
                $newToken['content'] = '#[';
51✔
1533
                $finalTokens[$newStackPtr] = $newToken;
51✔
1534
                $newStackPtr++;
51✔
1535

1536
                if ($bracketCloser !== null) {
51✔
1537
                    $tokens[$bracketCloser]    = [];
51✔
1538
                    $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1539
                    $tokens[$bracketCloser][1] = ']';
51✔
1540

1541
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1542
                        StatusWriter::write("* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END", 2);
×
1543
                    }
1544
                }
1545

1546
                continue;
51✔
1547
            }
1548

1549
            /*
1550
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1551
                token and ensures that the colon after it is always T_COLON.
1552
            */
1553

1554
            if ($tokenIsArray === true
3,615✔
1555
                && ($token[0] === T_STRING
3,615✔
1556
                || preg_match(self::PHP_LABEL_REGEX, $token[1]) === 1)
3,615✔
1557
            ) {
1558
                // Get the next non-empty token.
1559
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
3,429✔
1560
                    if (is_array($tokens[$i]) === false
3,429✔
1561
                        || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
3,429✔
1562
                    ) {
1563
                        break;
3,429✔
1564
                    }
1565
                }
1566

1567
                if (isset($tokens[$i]) === true
3,429✔
1568
                    && is_array($tokens[$i]) === false
3,429✔
1569
                    && $tokens[$i] === ':'
3,429✔
1570
                ) {
1571
                    // Get the previous non-empty token.
1572
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,872✔
1573
                        if (is_array($tokens[$j]) === false
1,872✔
1574
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$j][0]]) === false
1,872✔
1575
                        ) {
1576
                            break;
1,872✔
1577
                        }
1578
                    }
1579

1580
                    if (is_array($tokens[$j]) === false
1,872✔
1581
                        && ($tokens[$j] === '('
1,821✔
1582
                        || $tokens[$j] === ',')
1,872✔
1583
                    ) {
1584
                        $newToken            = [];
807✔
1585
                        $newToken['code']    = T_PARAM_NAME;
807✔
1586
                        $newToken['type']    = 'T_PARAM_NAME';
807✔
1587
                        $newToken['content'] = $token[1];
807✔
1588
                        $finalTokens[$newStackPtr] = $newToken;
807✔
1589

1590
                        $newStackPtr++;
807✔
1591

1592
                        // Modify the original token stack so that future checks, like
1593
                        // determining T_COLON vs T_INLINE_ELSE can handle this correctly.
1594
                        $tokens[$stackPtr][0] = T_PARAM_NAME;
807✔
1595

1596
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
807✔
1597
                            $type = Tokens::tokenName($token[0]);
×
1598
                            StatusWriter::write("* token $stackPtr changed from $type to T_PARAM_NAME", 2);
×
1599
                        }
1600

1601
                        continue;
807✔
1602
                    }
1603
                }
1604
            }
1605

1606
            /*
1607
                "readonly" keyword for PHP < 8.1
1608
            */
1609

1610
            if ($tokenIsArray === true
3,615✔
1611
                && strtolower($token[1]) === 'readonly'
3,615✔
1612
                && (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,846✔
1613
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
3,615✔
1614
            ) {
1615
                // Get the next non-whitespace token.
1616
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,089✔
1617
                    if (is_array($tokens[$i]) === false
1,089✔
1618
                        || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
1,089✔
1619
                    ) {
1620
                        break;
1,089✔
1621
                    }
1622
                }
1623

1624
                $isReadonlyKeyword = false;
1,089✔
1625

1626
                if (isset($tokens[$i]) === false
1,089✔
1627
                    || $tokens[$i] !== '('
1,089✔
1628
                ) {
1629
                    $isReadonlyKeyword = true;
1,089✔
1630
                } elseif ($tokens[$i] === '(') {
×
1631
                    /*
1632
                     * Skip over tokens which can be used in type declarations.
1633
                     * At this point, the only token types which need to be taken into consideration
1634
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1635
                     * and the union/intersection/dnf parentheses.
1636
                     */
1637

1638
                    $foundDNFParens = 1;
×
1639
                    $foundDNFPipe   = 0;
×
1640

1641
                    for (++$i; $i < $numTokens; $i++) {
×
1642
                        if (is_array($tokens[$i]) === true) {
×
1643
                            $tokenType = $tokens[$i][0];
×
1644
                        } else {
1645
                            $tokenType = $tokens[$i];
×
1646
                        }
1647

1648
                        if (isset(Tokens::EMPTY_TOKENS[$tokenType]) === true) {
×
1649
                            continue;
×
1650
                        }
1651

1652
                        if ($tokenType === '|') {
×
1653
                            ++$foundDNFPipe;
×
1654
                            continue;
×
1655
                        }
1656

1657
                        if ($tokenType === ')') {
×
1658
                            ++$foundDNFParens;
×
1659
                            continue;
×
1660
                        }
1661

1662
                        if ($tokenType === '(') {
×
1663
                            ++$foundDNFParens;
×
1664
                            continue;
×
1665
                        }
1666

1667
                        if ($tokenType === T_STRING
×
1668
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1669
                            || $tokenType === T_NAME_RELATIVE
×
1670
                            || $tokenType === T_NAME_QUALIFIED
×
1671
                            || $tokenType === T_ARRAY
×
1672
                            || $tokenType === T_NAMESPACE
×
1673
                            || $tokenType === T_NS_SEPARATOR
×
1674
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1675
                            || $tokenType === '&' // PHP < 8.0.
×
1676
                        ) {
1677
                            continue;
×
1678
                        }
1679

1680
                        // Reached the next token after.
1681
                        if (($foundDNFParens % 2) === 0
×
1682
                            && $foundDNFPipe >= 1
×
1683
                            && ($tokenType === T_VARIABLE
×
1684
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1685
                        ) {
1686
                            $isReadonlyKeyword = true;
×
1687
                        }
1688

1689
                        break;
×
1690
                    }
1691
                }
1692

1693
                if ($isReadonlyKeyword === true) {
1,089✔
1694
                    $finalTokens[$newStackPtr] = [
1,089✔
1695
                        'code'    => T_READONLY,
1,089✔
1696
                        'type'    => 'T_READONLY',
1,089✔
1697
                        'content' => $token[1],
1,089✔
1698
                    ];
726✔
1699
                    $newStackPtr++;
1,089✔
1700

1701
                    // Also modify the original token stack so that
1702
                    // future checks (like looking for T_NULLABLE) can
1703
                    // detect the T_READONLY token more easily.
1704
                    $tokens[$stackPtr][0] = T_READONLY;
1,089✔
1705
                    $token[0] = T_READONLY;
1,089✔
1706

1707
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
1,089✔
1708
                        StatusWriter::write("* token $stackPtr changed from $type to T_READONLY", 2);
363✔
1709
                    }
1710
                } else {
1711
                    $finalTokens[$newStackPtr] = [
×
1712
                        'code'    => T_STRING,
×
1713
                        'type'    => 'T_STRING',
×
1714
                        'content' => $token[1],
×
1715
                    ];
1716
                    $newStackPtr++;
×
1717

1718
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1719
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1720
                    }
1721
                }
1722

1723
                continue;
1,089✔
1724
            }
1725

1726
            /*
1727
                Deal with "yield from" in various PHP versions.
1728
            */
1729

1730
            if (PHP_VERSION_ID < 80300
3,615✔
1731
                && $tokenIsArray === true
3,615✔
1732
                && $token[0] === T_STRING
3,615✔
1733
                && strtolower($token[1]) === 'from'
3,615✔
1734
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
3,615✔
1735
            ) {
1736
                /*
1737
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1738
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1739
                    We want to keep the tokenization of the tokens between, but need to change the
1740
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1741
                */
1742

1743
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
18✔
1744
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
18✔
1745

1746
                $finalTokens[$newStackPtr] = [
18✔
1747
                    'code'    => T_YIELD_FROM,
18✔
1748
                    'type'    => 'T_YIELD_FROM',
18✔
1749
                    'content' => $token[1],
18✔
1750
                ];
1751
                $newStackPtr++;
18✔
1752

1753
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
1754
                    StatusWriter::write("* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD", 2);
×
1755
                    StatusWriter::write("* token $stackPtr changed into T_YIELD_FROM; was: T_STRING", 2);
×
1756
                }
1757

1758
                continue;
18✔
1759
            } elseif ($tokenIsArray === true
3,615✔
1760
                && $token[0] === T_YIELD_FROM
3,615✔
1761
                && strpos($token[1], $this->eolChar) !== false
3,615✔
1762
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
3,615✔
1763
            ) {
1764
                /*
1765
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1766
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1767
                    separately for consistency.
1768
                */
1769

1770
                $finalTokens[$newStackPtr] = [
54✔
1771
                    'code'    => T_YIELD_FROM,
54✔
1772
                    'type'    => 'T_YIELD_FROM',
54✔
1773
                    'content' => substr($token[1], 0, 5),
54✔
1774
                ];
36✔
1775
                $newStackPtr++;
54✔
1776

1777
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
54✔
1778
                $numLines   = count($tokenLines);
54✔
1779
                $newToken   = [
36✔
1780
                    'type'    => 'T_WHITESPACE',
54✔
1781
                    'code'    => T_WHITESPACE,
54✔
1782
                    'content' => '',
54✔
1783
                ];
36✔
1784

1785
                foreach ($tokenLines as $i => $line) {
54✔
1786
                    $newToken['content'] = $line;
54✔
1787
                    if ($i === ($numLines - 1)) {
54✔
1788
                        if ($line === '') {
54✔
1789
                            break;
18✔
1790
                        }
1791
                    } else {
1792
                        $newToken['content'] .= $this->eolChar;
54✔
1793
                    }
1794

1795
                    $finalTokens[$newStackPtr] = $newToken;
54✔
1796
                    $newStackPtr++;
54✔
1797
                }
1798

1799
                $finalTokens[$newStackPtr] = [
54✔
1800
                    'code'    => T_YIELD_FROM,
54✔
1801
                    'type'    => 'T_YIELD_FROM',
54✔
1802
                    'content' => substr($token[1], -4),
54✔
1803
                ];
36✔
1804
                $newStackPtr++;
54✔
1805

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

1810
                continue;
54✔
1811
            } elseif (PHP_VERSION_ID >= 80300
3,615✔
1812
                && $tokenIsArray === true
3,615✔
1813
                && $token[0] === T_YIELD_FROM
3,615✔
1814
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
3,615✔
1815
                && stripos($token[1], 'yield') === 0
3,615✔
1816
            ) {
1817
                /*
1818
                    Since PHP 8.3, "yield from" allows for comments and will
1819
                    swallow the comment in the `T_YIELD_FROM` token.
1820
                    We need to split this up to allow for sniffs handling comments.
1821
                */
1822

1823
                $finalTokens[$newStackPtr] = [
36✔
1824
                    'code'    => T_YIELD_FROM,
36✔
1825
                    'type'    => 'T_YIELD_FROM',
36✔
1826
                    'content' => substr($token[1], 0, 5),
36✔
1827
                ];
36✔
1828
                $newStackPtr++;
36✔
1829

1830
                $yieldFromSubtokens = @token_get_all("<?php\n" . substr($token[1], 5, -4));
36✔
1831
                // Remove the PHP open tag token.
1832
                array_shift($yieldFromSubtokens);
36✔
1833
                // Add the "from" keyword.
1834
                $yieldFromSubtokens[] = [
36✔
1835
                    0 => T_YIELD_FROM,
36✔
1836
                    1 => substr($token[1], -4),
36✔
1837
                ];
36✔
1838

1839
                // Inject the new tokens into the token stack.
1840
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
36✔
1841
                $numTokens = count($tokens);
36✔
1842

1843
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
36✔
1844
                    StatusWriter::write("* token $stackPtr split into parts (yield from with comment)", 2);
×
1845
                }
1846

1847
                unset($yieldFromSubtokens);
36✔
1848
                continue;
36✔
1849
            }
1850

1851
            /*
1852
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1853
                T_COALESCE, T_EQUAL.
1854
                So look for and combine these tokens in earlier versions.
1855
            */
1856

1857
            if ($tokenIsArray === true
3,615✔
1858
                && $token[0] === T_COALESCE
3,615✔
1859
                && isset($tokens[($stackPtr + 1)]) === true
3,615✔
1860
                && $tokens[($stackPtr + 1)][0] === '='
3,615✔
1861
            ) {
1862
                $newToken            = [];
×
1863
                $newToken['code']    = T_COALESCE_EQUAL;
×
1864
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1865
                $newToken['content'] = '??=';
×
1866
                $finalTokens[$newStackPtr] = $newToken;
×
1867

1868
                $newStackPtr++;
×
1869
                $stackPtr++;
×
1870

1871
                continue;
×
1872
            }
1873

1874
            /*
1875
                Before PHP 8, the ?-> operator was tokenized as
1876
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1877
                So look for and combine these tokens in earlier versions.
1878
            */
1879

1880
            if ($tokenIsArray === false
3,615✔
1881
                && $token[0] === '?'
3,615✔
1882
                && isset($tokens[($stackPtr + 1)]) === true
3,615✔
1883
                && is_array($tokens[($stackPtr + 1)]) === true
3,615✔
1884
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
3,615✔
1885
            ) {
1886
                $newToken            = [];
25✔
1887
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
25✔
1888
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
25✔
1889
                $newToken['content'] = '?->';
25✔
1890
                $finalTokens[$newStackPtr] = $newToken;
25✔
1891

1892
                $newStackPtr++;
25✔
1893
                $stackPtr++;
25✔
1894
                continue;
25✔
1895
            }
1896

1897
            /*
1898
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1899
                tokens split the token with a T_STRING. So look for
1900
                and change these tokens in earlier versions.
1901
            */
1902

1903
            if (PHP_VERSION_ID < 70400
3,615✔
1904
                && ($tokenIsArray === true
3,615✔
1905
                && ($token[0] === T_LNUMBER
3,615✔
1906
                || $token[0] === T_DNUMBER)
3,615✔
1907
                && isset($tokens[($stackPtr + 1)]) === true
3,615✔
1908
                && is_array($tokens[($stackPtr + 1)]) === true
3,615✔
1909
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,615✔
1910
                && $tokens[($stackPtr + 1)][1][0] === '_')
3,615✔
1911
            ) {
1912
                $newContent = $token[1];
27✔
1913
                $newType    = $token[0];
27✔
1914
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
27✔
1915
                    if (is_array($tokens[$i]) === false) {
27✔
1916
                        break;
27✔
1917
                    }
1918

1919
                    if ($tokens[$i][0] === T_LNUMBER
27✔
1920
                        || $tokens[$i][0] === T_DNUMBER
27✔
1921
                    ) {
1922
                        $newContent .= $tokens[$i][1];
27✔
1923
                        continue;
27✔
1924
                    }
1925

1926
                    if ($tokens[$i][0] === T_STRING
27✔
1927
                        && $tokens[$i][1][0] === '_'
27✔
1928
                        && ((strpos($newContent, '0x') === 0
27✔
1929
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
27✔
1930
                        || (strpos($newContent, '0x') !== 0
27✔
1931
                        && substr($newContent, -1) !== '.'
27✔
1932
                        && substr(strtolower($newContent), -1) !== 'e'
27✔
1933
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
27✔
1934
                    ) {
1935
                        $newContent .= $tokens[$i][1];
27✔
1936

1937
                        // Support floats.
1938
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
27✔
1939
                            && ($tokens[($i + 1)] === '-'
27✔
1940
                            || $tokens[($i + 1)] === '+')
27✔
1941
                        ) {
1942
                            $newContent .= $tokens[($i + 1)];
27✔
1943
                            $i++;
27✔
1944
                        }
1945

1946
                        continue;
27✔
1947
                    }
1948

1949
                    break;
27✔
1950
                }
1951

1952
                if ($newType === T_LNUMBER
27✔
1953
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1954
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1955
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1956
                    || (stripos($newContent, '0x') !== 0
27✔
1957
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
27✔
1958
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
27✔
1959
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1960
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
27✔
1961
                ) {
1962
                    $newType = T_DNUMBER;
27✔
1963
                }
1964

1965
                $newToken            = [];
27✔
1966
                $newToken['code']    = $newType;
27✔
1967
                $newToken['type']    = Tokens::tokenName($newType);
27✔
1968
                $newToken['content'] = $newContent;
27✔
1969
                $finalTokens[$newStackPtr] = $newToken;
27✔
1970

1971
                $newStackPtr++;
27✔
1972
                $stackPtr = ($i - 1);
27✔
1973
                continue;
27✔
1974
            }
1975

1976
            /*
1977
                Backfill the T_MATCH token for PHP versions < 8.0 and
1978
                do initial correction for non-match expression T_MATCH tokens
1979
                to T_STRING for PHP >= 8.0.
1980
                A final check for non-match expression T_MATCH tokens is done
1981
                in PHP::processAdditional().
1982
            */
1983

1984
            if ($tokenIsArray === true
3,615✔
1985
                && (($token[0] === T_STRING
3,615✔
1986
                && strtolower($token[1]) === 'match')
3,483✔
1987
                || $token[0] === T_MATCH)
3,615✔
1988
            ) {
1989
                $isMatch = false;
824✔
1990
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
824✔
1991
                    if (isset($tokens[$x][0], Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === true) {
824✔
1992
                        continue;
753✔
1993
                    }
1994

1995
                    if ($tokens[$x] !== '(') {
824✔
1996
                        // This is not a match expression.
1997
                        break;
428✔
1998
                    }
1999

2000
                    if (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
753✔
2001
                        // Also not a match expression.
2002
                        break;
183✔
2003
                    }
2004

2005
                    $isMatch = true;
753✔
2006
                    break;
753✔
2007
                }
2008

2009
                if ($isMatch === true && $token[0] === T_STRING) {
824✔
2010
                    $newToken            = [];
251✔
2011
                    $newToken['code']    = T_MATCH;
251✔
2012
                    $newToken['type']    = 'T_MATCH';
251✔
2013
                    $newToken['content'] = $token[1];
251✔
2014

2015
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
251✔
2016
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_MATCH", 2);
×
2017
                    }
2018

2019
                    $finalTokens[$newStackPtr] = $newToken;
251✔
2020
                    $newStackPtr++;
251✔
2021
                    continue;
251✔
2022
                } elseif ($isMatch === false && $token[0] === T_MATCH) {
808✔
2023
                    // PHP 8.0, match keyword, but not a match expression.
2024
                    $newToken            = [];
122✔
2025
                    $newToken['code']    = T_STRING;
122✔
2026
                    $newToken['type']    = 'T_STRING';
122✔
2027
                    $newToken['content'] = $token[1];
122✔
2028

2029
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
122✔
2030
                        StatusWriter::write("* token $stackPtr changed from T_MATCH to T_STRING", 2);
×
2031
                    }
2032

2033
                    $finalTokens[$newStackPtr] = $newToken;
122✔
2034
                    $newStackPtr++;
122✔
2035
                    continue;
122✔
2036
                }
2037
            }
2038

2039
            /*
2040
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
2041
                to prevent scope being set and the scope for switch default statements
2042
                breaking.
2043
            */
2044

2045
            if ($tokenIsArray === true
3,615✔
2046
                && $token[0] === T_DEFAULT
3,615✔
2047
                && isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,615✔
2048
            ) {
2049
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,491✔
2050
                    if ($tokens[$x] === ',') {
1,491✔
2051
                        // Skip over potential trailing comma (supported in PHP).
2052
                        continue;
231✔
2053
                    }
2054

2055
                    if (is_array($tokens[$x]) === false
1,491✔
2056
                        || isset(Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === false
1,491✔
2057
                    ) {
2058
                        // Non-empty, non-comma content.
2059
                        break;
1,491✔
2060
                    }
2061
                }
2062

2063
                if (isset($tokens[$x]) === true
1,491✔
2064
                    && is_array($tokens[$x]) === true
1,491✔
2065
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,491✔
2066
                ) {
2067
                    // Modify the original token stack for the double arrow so that
2068
                    // future checks can disregard the double arrow token more easily.
2069
                    // For match expression "case" statements, this is handled
2070
                    // in PHP::processAdditional().
2071
                    $tokens[$x][0] = T_MATCH_ARROW;
753✔
2072
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
753✔
2073
                        StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
2074
                    }
2075

2076
                    $newToken            = [];
753✔
2077
                    $newToken['code']    = T_MATCH_DEFAULT;
753✔
2078
                    $newToken['type']    = 'T_MATCH_DEFAULT';
753✔
2079
                    $newToken['content'] = $token[1];
753✔
2080

2081
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
753✔
2082
                        StatusWriter::write("* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT", 2);
×
2083
                    }
2084

2085
                    $finalTokens[$newStackPtr] = $newToken;
753✔
2086
                    $newStackPtr++;
753✔
2087
                    continue;
753✔
2088
                }
2089
            }
2090

2091
            /*
2092
                Convert ? to T_NULLABLE OR T_INLINE_THEN
2093
            */
2094

2095
            if ($tokenIsArray === false && $token[0] === '?') {
3,615✔
2096
                $newToken            = [];
1,407✔
2097
                $newToken['content'] = '?';
1,407✔
2098

2099
                // For typed constants, we only need to check the token before the ? to be sure.
2100
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,407✔
2101
                    $newToken['code'] = T_NULLABLE;
189✔
2102
                    $newToken['type'] = 'T_NULLABLE';
189✔
2103

2104
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
189✔
2105
                        StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2106
                    }
2107

2108
                    $finalTokens[$newStackPtr] = $newToken;
189✔
2109
                    $newStackPtr++;
189✔
2110
                    continue;
189✔
2111
                }
2112

2113
                /*
2114
                 * Check if the next non-empty token is one of the tokens which can be used
2115
                 * in type declarations. If not, it's definitely a ternary.
2116
                 * At this point, the only token types which need to be taken into consideration
2117
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
2118
                 */
2119

2120
                $lastRelevantNonEmpty = null;
1,407✔
2121

2122
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,407✔
2123
                    if (is_array($tokens[$i]) === true) {
1,407✔
2124
                        $tokenType = $tokens[$i][0];
1,407✔
2125
                    } else {
2126
                        $tokenType = $tokens[$i];
1,075✔
2127
                    }
2128

2129
                    if (isset(Tokens::EMPTY_TOKENS[$tokenType]) === true) {
1,407✔
2130
                        continue;
1,407✔
2131
                    }
2132

2133
                    if (isset(Tokens::NAME_TOKENS[$tokenType]) === true
1,407✔
2134
                        || $tokenType === T_ARRAY
1,407✔
2135
                        || $tokenType === T_NAMESPACE
1,407✔
2136
                        || $tokenType === T_NS_SEPARATOR
1,407✔
2137
                    ) {
2138
                        $lastRelevantNonEmpty = $tokenType;
1,075✔
2139
                        continue;
1,075✔
2140
                    }
2141

2142
                    $isInlineThen = false;
1,407✔
2143
                    if (isset($lastRelevantNonEmpty) === false && $tokenType !== T_CALLABLE) {
1,407✔
2144
                        // Can be anything, but is definitely not a type declaration.
2145
                        $isInlineThen = true;
980✔
2146
                    } elseif (isset($lastRelevantNonEmpty) === true) {
1,075✔
2147
                        if ($lastRelevantNonEmpty === T_ARRAY && $tokenType === '(') {
1,075✔
2148
                            // Array declaration in ternary then.
2149
                            $isInlineThen = true;
69✔
2150
                        } elseif (isset(Tokens::NAME_TOKENS[$lastRelevantNonEmpty]) === true
1,075✔
2151
                            && ($tokenType === T_DOUBLE_COLON
1,075✔
2152
                            || $tokenType === '('
1,075✔
2153
                            || $tokenType === ':')
1,075✔
2154
                        ) {
2155
                            // Constant access, function call, static class member access in ternary then.
2156
                            $isInlineThen = true;
868✔
2157
                        }
2158
                    }
2159

2160
                    if ($isInlineThen === true) {
1,407✔
2161
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,200✔
2162
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2163
                        }
2164

2165
                        $newToken['code'] = T_INLINE_THEN;
1,200✔
2166
                        $newToken['type'] = 'T_INLINE_THEN';
1,200✔
2167

2168
                        $insideInlineIf[] = $stackPtr;
1,200✔
2169

2170
                        $finalTokens[$newStackPtr] = $newToken;
1,200✔
2171
                        $newStackPtr++;
1,200✔
2172
                        continue 2;
1,200✔
2173
                    }
2174

2175
                    break;
276✔
2176
                }
2177

2178
                /*
2179
                 * This can still be a nullable type or a ternary.
2180
                 * Do additional checking.
2181
                 */
2182

2183
                $prevNonEmpty     = null;
297✔
2184
                $lastSeenNonEmpty = null;
297✔
2185

2186
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
297✔
2187
                    if (is_array($tokens[$i]) === true) {
297✔
2188
                        $tokenType = $tokens[$i][0];
297✔
2189
                    } else {
2190
                        $tokenType = $tokens[$i];
297✔
2191
                    }
2192

2193
                    if ($tokenType === T_STATIC
297✔
2194
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
198✔
2195
                        || $lastSeenNonEmpty === '(')
297✔
2196
                    ) {
2197
                        $lastSeenNonEmpty = $tokenType;
×
2198
                        continue;
×
2199
                    }
2200

2201
                    if ($prevNonEmpty === null
297✔
2202
                        && @isset(Tokens::EMPTY_TOKENS[$tokenType]) === false
297✔
2203
                    ) {
2204
                        // Found the previous non-empty token.
2205
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
297✔
2206
                            $newToken['code'] = T_NULLABLE;
276✔
2207
                            $newToken['type'] = 'T_NULLABLE';
276✔
2208

2209
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
276✔
2210
                                StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2211
                            }
2212

2213
                            break;
276✔
2214
                        }
2215

2216
                        $prevNonEmpty = $tokenType;
297✔
2217
                    }
2218

2219
                    if ($tokenType === T_FUNCTION
297✔
2220
                        || $tokenType === T_FN
297✔
2221
                        || @isset(Tokens::METHOD_MODIFIERS[$tokenType]) === true
297✔
2222
                        || @isset(Tokens::SCOPE_MODIFIERS[$tokenType]) === true
297✔
2223
                        || $tokenType === T_VAR
297✔
2224
                        || $tokenType === T_READONLY
297✔
2225
                    ) {
2226
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
276✔
2227
                            StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2228
                        }
2229

2230
                        $newToken['code'] = T_NULLABLE;
276✔
2231
                        $newToken['type'] = 'T_NULLABLE';
276✔
2232
                        break;
276✔
2233
                    } elseif (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
297✔
2234
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
90✔
2235
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2236
                        }
2237

2238
                        $newToken['code'] = T_INLINE_THEN;
90✔
2239
                        $newToken['type'] = 'T_INLINE_THEN';
90✔
2240

2241
                        $insideInlineIf[] = $stackPtr;
90✔
2242
                        break;
90✔
2243
                    }
2244

2245
                    if (@isset(Tokens::EMPTY_TOKENS[$tokenType]) === false) {
297✔
2246
                        $lastSeenNonEmpty = $tokenType;
297✔
2247
                    }
2248
                }
2249

2250
                $finalTokens[$newStackPtr] = $newToken;
297✔
2251
                $newStackPtr++;
297✔
2252
                continue;
297✔
2253
            }
2254

2255
            /*
2256
                Tokens after a double colon may look like scope openers,
2257
                such as when writing code like Foo::NAMESPACE, but they are
2258
                only ever variables or strings.
2259
            */
2260

2261
            if ($stackPtr > 1
3,615✔
2262
                && (is_array($tokens[($stackPtr - 1)]) === true
3,615✔
2263
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
3,615✔
2264
                && $tokenIsArray === true
3,615✔
2265
                && $token[0] !== T_STRING
3,615✔
2266
                && $token[0] !== T_VARIABLE
3,615✔
2267
                && $token[0] !== T_DOLLAR
3,615✔
2268
                && isset(Tokens::EMPTY_TOKENS[$token[0]]) === false
3,615✔
2269
            ) {
2270
                $newToken            = [];
×
2271
                $newToken['code']    = T_STRING;
×
2272
                $newToken['type']    = 'T_STRING';
×
2273
                $newToken['content'] = $token[1];
×
2274
                $finalTokens[$newStackPtr] = $newToken;
×
2275

2276
                $newStackPtr++;
×
2277
                continue;
×
2278
            }
2279

2280
            /*
2281
                Backfill the T_FN token for PHP versions < 7.4.
2282
            */
2283

2284
            if ($tokenIsArray === true
3,615✔
2285
                && $token[0] === T_STRING
3,615✔
2286
                && strtolower($token[1]) === 'fn'
3,615✔
2287
                && isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,615✔
2288
                && $finalTokens[$lastNotEmptyToken]['content'] !== '&'
3,615✔
2289
            ) {
2290
                // Modify the original token stack so that
2291
                // future checks (like looking for T_NULLABLE) can
2292
                // detect the T_FN token more easily.
2293
                $tokens[$stackPtr][0] = T_FN;
653✔
2294
                $token[0] = T_FN;
653✔
2295
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
653✔
2296
                    StatusWriter::write("* token $stackPtr changed from T_STRING to T_FN", 2);
×
2297
                }
2298
            }
2299

2300
            /*
2301
                This is a special condition for T_ARRAY tokens used for
2302
                function return types. We want to keep the parenthesis map clean,
2303
                so let's tag these tokens as T_STRING.
2304
            */
2305

2306
            if ($tokenIsArray === true
3,615✔
2307
                && ($token[0] === T_FUNCTION
3,615✔
2308
                || $token[0] === T_FN)
3,615✔
2309
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
3,615✔
2310
            ) {
2311
                // Go looking for the colon to start the return type hint.
2312
                // Start by finding the closing parenthesis of the function.
2313
                $parenthesisStack  = [];
2,889✔
2314
                $parenthesisCloser = false;
2,889✔
2315
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,889✔
2316
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,889✔
2317
                        $parenthesisStack[] = $x;
2,889✔
2318
                    } elseif (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,889✔
2319
                        array_pop($parenthesisStack);
2,889✔
2320
                        if (empty($parenthesisStack) === true) {
2,889✔
2321
                            $parenthesisCloser = $x;
2,889✔
2322
                            break;
2,889✔
2323
                        }
2324
                    }
2325
                }
2326

2327
                if ($parenthesisCloser !== false) {
2,889✔
2328
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,889✔
2329
                        if (is_array($tokens[$x]) === false
2,889✔
2330
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === false
2,889✔
2331
                        ) {
2332
                            // Non-empty content.
2333
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,889✔
2334
                                // Found a use statement, so search ahead for the closing parenthesis.
2335
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
2336
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
2337
                                        continue(2);
57✔
2338
                                    }
2339
                                }
2340
                            }
2341

2342
                            break;
2,889✔
2343
                        }
2344
                    }
2345

2346
                    if (isset($tokens[$x]) === true
2,889✔
2347
                        && is_array($tokens[$x]) === false
2,889✔
2348
                        && $tokens[$x] === ':'
2,889✔
2349
                    ) {
2350
                        // Find the start of the return type.
2351
                        for ($x += 1; $x < $numTokens; $x++) {
2,133✔
2352
                            if (is_array($tokens[$x]) === true
2,133✔
2353
                                && isset(Tokens::EMPTY_TOKENS[$tokens[$x][0]]) === true
2,133✔
2354
                            ) {
2355
                                // Whitespace or comments before the return type.
2356
                                continue;
2,133✔
2357
                            }
2358

2359
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
2,133✔
2360
                                // Found a nullable operator, so skip it.
2361
                                // But also convert the token to save the tokenizer
2362
                                // a bit of time later on.
2363
                                $tokens[$x] = [
276✔
2364
                                    T_NULLABLE,
276✔
2365
                                    '?',
276✔
2366
                                ];
184✔
2367

2368
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
276✔
2369
                                    StatusWriter::write("* token $x changed from ? to T_NULLABLE", 2);
×
2370
                                }
2371

2372
                                continue;
276✔
2373
                            }
2374

2375
                            break;
2,133✔
2376
                        }
2377
                    }
2378
                }
2379
            }
2380

2381
            /*
2382
                PHP doesn't assign a token to goto labels, so we have to.
2383
                These are just string tokens with a single colon after them. Double
2384
                colons are already tokenized and so don't interfere with this check.
2385
                But we do have to account for CASE statements, that look just like
2386
                goto labels.
2387
            */
2388

2389
            if ($tokenIsArray === true
3,615✔
2390
                && ($token[0] === T_STRING
3,615✔
2391
                || ($token[0] === T_EXIT
3,615✔
2392
                && (strtolower($token[1]) === 'exit' || strtolower($token[1]) === 'die')))
3,615✔
2393
                && (is_array($tokens[($stackPtr - 1)]) === false
3,483✔
2394
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
3,615✔
2395
            ) {
2396
                // Find next non-empty token.
2397
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
3,213✔
2398
                    if (is_array($tokens[$i]) === true
3,213✔
2399
                        && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
3,213✔
2400
                    ) {
2401
                        continue;
2,886✔
2402
                    }
2403

2404
                    break;
3,213✔
2405
                }
2406

2407
                if (isset($tokens[$i]) === true && $tokens[$i] === ':') {
3,213✔
2408
                    // Okay, so we have a colon, now we need to make sure that this is not
2409
                    // class constant access, a ternary, enum or switch case.
2410
                    $stopTokens = [
1,092✔
2411
                        T_DOUBLE_COLON       => true,
1,638✔
2412
                        T_CASE               => true,
1,638✔
2413
                        T_SEMICOLON          => true,
1,638✔
2414
                        T_OPEN_TAG           => true,
1,638✔
2415
                        T_OPEN_CURLY_BRACKET => true,
1,638✔
2416
                        T_INLINE_THEN        => true,
1,638✔
2417
                        T_ENUM               => true,
1,638✔
2418
                    ];
1,092✔
2419

2420
                    for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,638✔
2421
                        if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,638✔
2422
                            break;
1,638✔
2423
                        }
2424
                    }
2425

2426
                    if ($finalTokens[$x]['code'] !== T_DOUBLE_COLON
1,638✔
2427
                        && $finalTokens[$x]['code'] !== T_CASE
1,638✔
2428
                        && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,638✔
2429
                        && $finalTokens[$x]['code'] !== T_ENUM
1,638✔
2430
                    ) {
2431
                        $finalTokens[$newStackPtr] = [
264✔
2432
                            'content' => $token[1],
264✔
2433
                            'code'    => T_GOTO_LABEL,
264✔
2434
                            'type'    => 'T_GOTO_LABEL',
264✔
2435
                        ];
176✔
2436

2437
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
264✔
2438
                            StatusWriter::write("* token $stackPtr changed from T_STRING to T_GOTO_LABEL", 2);
×
2439
                        }
2440

2441
                        // Modify the original token stack for the colon as potential
2442
                        // whitespace/comments between still needs to get the normal treatment.
2443
                        $tokens[$i] = [
264✔
2444
                            0 => T_GOTO_COLON,
264✔
2445
                            1 => ':',
264✔
2446
                        ];
176✔
2447

2448
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
264✔
2449
                            StatusWriter::write("* token $i changed from \":\" to T_GOTO_COLON in the original token stack", 2);
×
2450
                        }
2451

2452
                        $newStackPtr++;
264✔
2453
                        continue;
264✔
2454
                    }
2455
                }
2456
            }
2457

2458
            /*
2459
                If this token has newlines in its content, split each line up
2460
                and create a new token for each line. We do this so it's easier
2461
                to ascertain where errors occur on a line.
2462
                Note that $token[1] is the token's content.
2463
            */
2464

2465
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
3,615✔
2466
                $tokenLines = explode($this->eolChar, $token[1]);
3,615✔
2467
                $numLines   = count($tokenLines);
3,615✔
2468
                $newToken   = [
2,412✔
2469
                    'type'    => Tokens::tokenName($token[0]),
3,615✔
2470
                    'code'    => $token[0],
3,615✔
2471
                    'content' => '',
3,615✔
2472
                ];
2,412✔
2473

2474
                for ($i = 0; $i < $numLines; $i++) {
3,615✔
2475
                    $newToken['content'] = $tokenLines[$i];
3,615✔
2476
                    if ($i === ($numLines - 1)) {
3,615✔
2477
                        if ($tokenLines[$i] === '') {
3,615✔
2478
                            break;
3,615✔
2479
                        }
2480
                    } else {
2481
                        $newToken['content'] .= $this->eolChar;
3,615✔
2482
                    }
2483

2484
                    $finalTokens[$newStackPtr] = $newToken;
3,615✔
2485
                    $newStackPtr++;
3,615✔
2486
                }
2487
            } else {
2488
                // Some T_STRING tokens should remain that way due to their context.
2489
                if ($tokenIsArray === true && $token[0] === T_STRING) {
3,615✔
2490
                    $preserveTstring = false;
3,213✔
2491

2492
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2493
                    // but the constant name should not be.
2494
                    if ((isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
3,213✔
2495
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
3,099✔
2496
                        || $insideConstDeclaration === true
3,213✔
2497
                    ) {
2498
                        // Find the next non-empty token.
2499
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,720✔
2500
                            if (is_array($tokens[$i]) === true
1,720✔
2501
                                && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
1,720✔
2502
                            ) {
2503
                                continue;
1,513✔
2504
                            }
2505

2506
                            break;
1,720✔
2507
                        }
2508

2509
                        if ($tokens[$i] === '=') {
1,720✔
2510
                            $preserveTstring        = true;
1,459✔
2511
                            $insideConstDeclaration = false;
1,546✔
2512
                        }
2513
                    } elseif (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
3,213✔
2514
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
3,213✔
2515
                    ) {
2516
                        $preserveTstring = true;
3,099✔
2517

2518
                        // Special case for syntax like: return new self/new parent
2519
                        // where self/parent should not be a string.
2520
                        $tokenContentLower = strtolower($token[1]);
3,099✔
2521
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
3,099✔
2522
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
3,099✔
2523
                        ) {
2524
                            $preserveTstring = false;
1,797✔
2525
                        }
2526
                    } elseif ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,970✔
2527
                        // Function names for functions declared to return by reference.
2528
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,077✔
2529
                            if (isset(Tokens::EMPTY_TOKENS[$finalTokens[$i]['code']]) === true) {
1,077✔
2530
                                continue;
498✔
2531
                            }
2532

2533
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,077✔
2534
                                $preserveTstring = true;
498✔
2535
                            }
2536

2537
                            break;
1,077✔
2538
                        }
2539
                    } else {
2540
                        // Keywords with special PHPCS token when used as a function call.
2541
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,970✔
2542
                            if (is_array($tokens[$i]) === true
2,970✔
2543
                                && isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === true
2,970✔
2544
                            ) {
2545
                                continue;
2,457✔
2546
                            }
2547

2548
                            if ($tokens[$i][0] === '(') {
2,970✔
2549
                                $preserveTstring = true;
2,547✔
2550
                            }
2551

2552
                            break;
2,970✔
2553
                        }
2554
                    }
2555

2556
                    if ($preserveTstring === true) {
3,213✔
2557
                        $finalTokens[$newStackPtr] = [
3,102✔
2558
                            'code'    => T_STRING,
3,102✔
2559
                            'type'    => 'T_STRING',
3,102✔
2560
                            'content' => $token[1],
3,102✔
2561
                        ];
2,068✔
2562

2563
                        $newStackPtr++;
3,102✔
2564
                        continue;
3,102✔
2565
                    }
2566
                }
2567

2568
                $newToken = null;
3,615✔
2569
                if ($tokenIsArray === false) {
3,615✔
2570
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3,603✔
2571
                        $newToken = self::$resolveTokenCache[$token[0]];
3,603✔
2572
                    }
2573
                } else {
2574
                    $cacheKey = null;
3,615✔
2575
                    if ($token[0] === T_STRING) {
3,615✔
2576
                        $cacheKey = strtolower($token[1]);
2,859✔
2577
                    } elseif ($token[0] !== T_CURLY_OPEN) {
3,615✔
2578
                        $cacheKey = $token[0];
3,615✔
2579
                    }
2580

2581
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,615✔
2582
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,615✔
2583
                        $newToken['content'] = $token[1];
3,615✔
2584
                    }
2585
                }
2586

2587
                if ($newToken === null) {
3,615✔
2588
                    $newToken = self::standardiseToken($token);
17✔
2589
                }
2590

2591
                // Convert colons that are actually the ELSE component of an
2592
                // inline IF statement.
2593
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
3,615✔
2594
                    $isInlineIf = true;
1,200✔
2595

2596
                    // Make sure this isn't a named parameter label.
2597
                    // Get the previous non-empty token.
2598
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,200✔
2599
                        if (is_array($tokens[$i]) === false
1,200✔
2600
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
1,200✔
2601
                        ) {
2602
                            break;
1,200✔
2603
                        }
2604
                    }
2605

2606
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,200✔
2607
                        $isInlineIf = false;
648✔
2608
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
648✔
2609
                            StatusWriter::write('* token is parameter label, not T_INLINE_ELSE', 2);
×
2610
                        }
2611
                    }
2612

2613
                    if ($isInlineIf === true) {
1,200✔
2614
                        // Make sure this isn't a return type separator.
2615
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,200✔
2616
                            if (is_array($tokens[$i]) === false
1,200✔
2617
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,200✔
2618
                                && $tokens[$i][0] !== T_COMMENT
1,200✔
2619
                                && $tokens[$i][0] !== T_WHITESPACE)
1,200✔
2620
                            ) {
2621
                                break;
1,200✔
2622
                            }
2623
                        }
2624

2625
                        if ($tokens[$i] === ')') {
1,200✔
2626
                            $parenCount = 1;
717✔
2627
                            for ($i--; $i > 0; $i--) {
717✔
2628
                                if ($tokens[$i] === '(') {
717✔
2629
                                    $parenCount--;
717✔
2630
                                    if ($parenCount === 0) {
717✔
2631
                                        break;
717✔
2632
                                    }
2633
                                } elseif ($tokens[$i] === ')') {
648✔
2634
                                    $parenCount++;
×
2635
                                }
2636
                            }
2637

2638
                            // We've found the open parenthesis, so if the previous
2639
                            // non-empty token is FUNCTION or USE, this is a return type.
2640
                            // Note that we need to skip T_STRING tokens here as these
2641
                            // can be function names.
2642
                            for ($i--; $i > 0; $i--) {
717✔
2643
                                if (is_array($tokens[$i]) === false
717✔
2644
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
717✔
2645
                                    && $tokens[$i][0] !== T_COMMENT
717✔
2646
                                    && $tokens[$i][0] !== T_WHITESPACE
717✔
2647
                                    && $tokens[$i][0] !== T_STRING)
717✔
2648
                                ) {
2649
                                    break;
717✔
2650
                                }
2651
                            }
2652

2653
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
717✔
2654
                                $isInlineIf = false;
648✔
2655
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
648✔
2656
                                    StatusWriter::write('* token is return type, not T_INLINE_ELSE', 2);
×
2657
                                }
2658
                            }
2659
                        }
2660
                    }
2661

2662
                    // Check to see if this is a CASE or DEFAULT opener.
2663
                    if ($isInlineIf === true) {
1,200✔
2664
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,200✔
2665
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,200✔
2666
                            if (is_array($tokens[$i]) === true
1,200✔
2667
                                && ($tokens[$i][0] === T_CASE
1,200✔
2668
                                || $tokens[$i][0] === T_DEFAULT)
1,200✔
2669
                            ) {
2670
                                $isInlineIf = false;
×
2671
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2672
                                    StatusWriter::write('* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE', 2);
×
2673
                                }
2674

2675
                                break;
×
2676
                            }
2677

2678
                            if (is_array($tokens[$i]) === false
1,200✔
2679
                                && ($tokens[$i] === ';'
1,200✔
2680
                                || $tokens[$i] === '{'
1,200✔
2681
                                || $tokens[$i] === '}')
1,200✔
2682
                            ) {
2683
                                break;
831✔
2684
                            }
2685
                        }
2686
                    }
2687

2688
                    if ($isInlineIf === true) {
1,200✔
2689
                        array_pop($insideInlineIf);
1,200✔
2690
                        $newToken['code'] = T_INLINE_ELSE;
1,200✔
2691
                        $newToken['type'] = 'T_INLINE_ELSE';
1,200✔
2692

2693
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,200✔
2694
                            StatusWriter::write('* token changed from T_COLON to T_INLINE_ELSE', 2);
×
2695
                        }
2696
                    }
2697
                }
2698

2699
                // This is a special condition for T_ARRAY tokens used for anything else
2700
                // but array declarations, like type hinting function arguments as
2701
                // being arrays.
2702
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2703
                // T_STRING.
2704
                if ($newToken['code'] === T_ARRAY) {
3,615✔
2705
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,617✔
2706
                        if (is_array($tokens[$i]) === false
1,617✔
2707
                            || isset(Tokens::EMPTY_TOKENS[$tokens[$i][0]]) === false
1,617✔
2708
                        ) {
2709
                            // Non-empty content.
2710
                            break;
1,617✔
2711
                        }
2712
                    }
2713

2714
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,617✔
2715
                        $newToken['code'] = T_STRING;
1,317✔
2716
                        $newToken['type'] = 'T_STRING';
1,317✔
2717
                    }
2718
                }
2719

2720
                // This is a special case for PHP 5.6 use function and use const
2721
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2722
                // and T_CONST.
2723
                if (($newToken['code'] === T_FUNCTION
3,615✔
2724
                    || $newToken['code'] === T_CONST)
3,615✔
2725
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
3,615✔
2726
                ) {
2727
                    $newToken['code'] = T_STRING;
207✔
2728
                    $newToken['type'] = 'T_STRING';
207✔
2729
                }
2730

2731
                // This is a special case for use groups in PHP 7+ where leaving
2732
                // the curly braces as their normal tokens would confuse
2733
                // the scope map and sniffs.
2734
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
3,615✔
2735
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
3,615✔
2736
                ) {
2737
                    $newToken['code'] = T_OPEN_USE_GROUP;
207✔
2738
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
207✔
2739
                    $insideUseGroup   = true;
207✔
2740
                }
2741

2742
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,615✔
2743
                    $newToken['code'] = T_CLOSE_USE_GROUP;
207✔
2744
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
207✔
2745
                    $insideUseGroup   = false;
207✔
2746
                }
2747

2748
                $finalTokens[$newStackPtr] = $newToken;
3,615✔
2749
                $newStackPtr++;
3,615✔
2750
            }
2751
        }
2752

2753
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,615✔
2754
            StatusWriter::write('*** END PHP TOKENIZING ***', 1);
×
2755
        }
2756

2757
        return $finalTokens;
3,615✔
2758
    }
2759

2760

2761
    /**
2762
     * Performs additional processing after main tokenizing.
2763
     *
2764
     * This additional processing checks for CASE statements that are using curly
2765
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2766
     * into T_CLOSURE when they are not standard function definitions. It also
2767
     * detects short array syntax and converts those square brackets into new tokens.
2768
     * It also corrects some usage of the static and class keywords. It also
2769
     * assigns tokens to function return types.
2770
     *
2771
     * @return void
2772
     */
2773
    protected function processAdditional()
1,944✔
2774
    {
2775
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,944✔
2776
            StatusWriter::write('*** START ADDITIONAL PHP PROCESSING ***', 1);
×
2777
        }
2778

2779
        $this->createAttributesNestingMap();
1,944✔
2780

2781
        $numTokens         = count($this->tokens);
1,944✔
2782
        $lastSeenTypeToken = $numTokens;
1,944✔
2783

2784
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,944✔
2785
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2786
            if (isset($this->tokens[$i]['scope_opener']) === true
1,944✔
2787
                && isset($this->tokens[$i]['scope_condition']) === false
1,944✔
2788
            ) {
2789
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2790
            }
2791

2792
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,944✔
2793
                /*
2794
                    Detect functions that are actually closures and
2795
                    assign them a different token.
2796
                */
2797

2798
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,641✔
2799
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,641✔
2800
                        if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false
1,641✔
2801
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,641✔
2802
                        ) {
2803
                            break;
1,641✔
2804
                        }
2805
                    }
2806

2807
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,641✔
2808
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,152✔
2809
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,152✔
2810
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,152✔
2811
                            $line = $this->tokens[$i]['line'];
×
2812
                            StatusWriter::write("* token $i on line $line changed from T_FUNCTION to T_CLOSURE", 1);
×
2813
                        }
2814

2815
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,152✔
2816
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
684✔
2817
                                continue;
×
2818
                            }
2819

2820
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
684✔
2821
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
684✔
2822
                                $type = $this->tokens[$x]['type'];
×
2823
                                StatusWriter::write("* cleaned $x ($type) *", 2);
×
2824
                            }
2825
                        }
2826
                    }
2827
                }
2828

2829
                continue;
1,641✔
2830
            } elseif ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,944✔
2831
                /*
2832
                    Detect anonymous classes and assign them a different token.
2833
                */
2834

2835
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,794✔
2836
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
1,794✔
2837
                        break;
1,794✔
2838
                    }
2839
                }
2840

2841
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,794✔
2842
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,794✔
2843
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,767✔
2844
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,794✔
2845
                ) {
2846
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,263✔
2847
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,263✔
2848
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,263✔
2849
                        $line = $this->tokens[$i]['line'];
×
2850
                        StatusWriter::write("* token $i on line $line changed from T_CLASS to T_ANON_CLASS", 1);
×
2851
                    }
2852

2853
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,263✔
2854
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,263✔
2855
                    ) {
2856
                        $closer = $this->tokens[$x]['parenthesis_closer'];
795✔
2857

2858
                        $this->tokens[$i]['parenthesis_opener']     = $x;
795✔
2859
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
795✔
2860
                        $this->tokens[$i]['parenthesis_owner']      = $i;
795✔
2861
                        $this->tokens[$x]['parenthesis_owner']      = $i;
795✔
2862
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
795✔
2863

2864
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
795✔
2865
                            $line = $this->tokens[$i]['line'];
×
2866
                            StatusWriter::write("* added parenthesis keys to T_ANON_CLASS token $i on line $line", 2);
×
2867
                        }
2868
                    }
2869

2870
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,263✔
2871
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,140✔
2872
                            continue;
×
2873
                        }
2874

2875
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,140✔
2876
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,140✔
2877
                            $type = $this->tokens[$x]['type'];
×
2878
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
2879
                        }
2880
                    }
2881
                }
2882

2883
                continue;
1,794✔
2884
            } elseif ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,944✔
2885
                // Possible arrow function.
2886
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,236✔
2887
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false
1,236✔
2888
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,236✔
2889
                    ) {
2890
                        // Non-whitespace content.
2891
                        break;
1,236✔
2892
                    }
2893
                }
2894

2895
                if (isset($this->tokens[$x]) === true
1,236✔
2896
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,236✔
2897
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,236✔
2898
                ) {
2899
                    $ignore  = Tokens::EMPTY_TOKENS;
1,233✔
2900
                    $ignore += Tokens::NAME_TOKENS;
1,233✔
2901
                    $ignore += [
822✔
2902
                        T_ARRAY                  => T_ARRAY,
1,233✔
2903
                        T_CALLABLE               => T_CALLABLE,
1,233✔
2904
                        T_COLON                  => T_COLON,
1,233✔
2905
                        T_NULL                   => T_NULL,
1,233✔
2906
                        T_TRUE                   => T_TRUE,
1,233✔
2907
                        T_FALSE                  => T_FALSE,
1,233✔
2908
                        T_NULLABLE               => T_NULLABLE,
1,233✔
2909
                        T_PARENT                 => T_PARENT,
1,233✔
2910
                        T_SELF                   => T_SELF,
1,233✔
2911
                        T_STATIC                 => T_STATIC,
1,233✔
2912
                        T_TYPE_UNION             => T_TYPE_UNION,
1,233✔
2913
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,233✔
2914
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,233✔
2915
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,233✔
2916
                    ];
822✔
2917

2918
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,233✔
2919
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,233✔
2920
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,233✔
2921
                            break;
1,233✔
2922
                        }
2923
                    }
2924

2925
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,233✔
2926
                        $endTokens = [
822✔
2927
                            T_COLON                => true,
1,233✔
2928
                            T_COMMA                => true,
1,233✔
2929
                            T_SEMICOLON            => true,
1,233✔
2930
                            T_CLOSE_PARENTHESIS    => true,
1,233✔
2931
                            T_CLOSE_SQUARE_BRACKET => true,
1,233✔
2932
                            T_CLOSE_CURLY_BRACKET  => true,
1,233✔
2933
                            T_CLOSE_SHORT_ARRAY    => true,
1,233✔
2934
                            T_OPEN_TAG             => true,
1,233✔
2935
                            T_CLOSE_TAG            => true,
1,233✔
2936
                        ];
822✔
2937

2938
                        $inTernary    = false;
1,233✔
2939
                        $lastEndToken = null;
1,233✔
2940

2941
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,233✔
2942
                            // Arrow function closer should never be shared with the closer of a match
2943
                            // control structure.
2944
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,233✔
2945
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,233✔
2946
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,233✔
2947
                            ) {
2948
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
438✔
2949
                                    // Match in return value of arrow function. Move on to the next token.
2950
                                    continue;
438✔
2951
                                }
2952

2953
                                // Arrow function as return value for the last match case without trailing comma.
2954
                                if ($lastEndToken !== null) {
438✔
2955
                                    $scopeCloser = $lastEndToken;
438✔
2956
                                    break;
438✔
2957
                                }
2958

2959
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
201✔
2960
                                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$lastNonEmpty]['code']]) === false) {
201✔
2961
                                        $scopeCloser = $lastNonEmpty;
201✔
2962
                                        break 2;
201✔
2963
                                    }
2964
                                }
2965
                            }
2966

2967
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,233✔
2968
                                if ($lastEndToken !== null
1,233✔
2969
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
1,077✔
2970
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
889✔
2971
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
1,077✔
2972
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
1,233✔
2973
                                ) {
2974
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
201✔
2975
                                        if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$lastNonEmpty]['code']]) === false) {
201✔
2976
                                            $scopeCloser = $lastNonEmpty;
201✔
2977
                                            break;
201✔
2978
                                        }
2979
                                    }
2980
                                }
2981

2982
                                break;
1,233✔
2983
                            }
2984

2985
                            if ($inTernary === false
1,233✔
2986
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,233✔
2987
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,233✔
2988
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,233✔
2989
                            ) {
2990
                                // Found a nested arrow function that already has the closer set and is in
2991
                                // the same scope as us, so we can use its closer.
2992
                                break;
201✔
2993
                            }
2994

2995
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,233✔
2996
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,233✔
2997
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,233✔
2998
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,233✔
2999
                            ) {
3000
                                // We minus 1 here in case the closer can be shared with us.
3001
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
438✔
3002
                                continue;
438✔
3003
                            }
3004

3005
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,233✔
3006
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
765✔
3007
                                $lastEndToken = $scopeCloser;
765✔
3008
                                continue;
765✔
3009
                            }
3010

3011
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,233✔
3012
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
201✔
3013
                                $lastEndToken = $scopeCloser;
201✔
3014
                                continue;
201✔
3015
                            }
3016

3017
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,233✔
3018
                                $inTernary = true;
201✔
3019
                                continue;
201✔
3020
                            }
3021

3022
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,233✔
3023
                                if ($inTernary === false) {
201✔
3024
                                    break;
201✔
3025
                                }
3026

3027
                                $inTernary = false;
201✔
3028
                                continue;
201✔
3029
                            }
3030
                        }
3031

3032
                        if ($scopeCloser !== $numTokens) {
1,233✔
3033
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,233✔
3034
                                $line = $this->tokens[$i]['line'];
×
3035
                                StatusWriter::write("=> token $i on line $line processed as arrow function", 1);
×
3036
                                StatusWriter::write("* scope opener set to $arrow *", 2);
×
3037
                                StatusWriter::write("* scope closer set to $scopeCloser *", 2);
×
3038
                                StatusWriter::write("* parenthesis opener set to $x *", 2);
×
3039
                                StatusWriter::write("* parenthesis closer set to $closer *", 2);
×
3040
                            }
3041

3042
                            $this->tokens[$i]['code']            = T_FN;
1,233✔
3043
                            $this->tokens[$i]['type']            = 'T_FN';
1,233✔
3044
                            $this->tokens[$i]['scope_condition'] = $i;
1,233✔
3045
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,233✔
3046
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,233✔
3047
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,233✔
3048
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,233✔
3049
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,233✔
3050

3051
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,233✔
3052
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,233✔
3053

3054
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,233✔
3055
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,233✔
3056
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,233✔
3057
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,233✔
3058
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,233✔
3059
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,233✔
3060

3061
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,233✔
3062
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,233✔
3063
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,233✔
3064
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,233✔
3065

3066
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,233✔
3067
                                $line = $this->tokens[$arrow]['line'];
×
3068
                                StatusWriter::write("* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW", 2);
×
3069
                            }
3070
                        }
3071
                    }
3072
                }
3073

3074
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
3075
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,236✔
3076
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
204✔
3077
                        $line = $this->tokens[$i]['line'];
×
3078
                        StatusWriter::write("=> token $i on line $line is not an arrow function", 1);
×
3079
                        StatusWriter::write('* token changed from T_FN to T_STRING', 2);
×
3080
                    }
3081

3082
                    $this->tokens[$i]['code'] = T_STRING;
204✔
3083
                    $this->tokens[$i]['type'] = 'T_STRING';
548✔
3084
                }
3085
            } elseif ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,944✔
3086
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,260✔
3087
                    continue;
123✔
3088
                }
3089

3090
                // Unless there is a variable or a bracket before this token,
3091
                // it is the start of an array being defined using the short syntax.
3092
                $isShortArray = false;
1,260✔
3093
                $allowed      = Tokens::NAME_TOKENS;
1,260✔
3094
                $allowed     += [
840✔
3095
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,260✔
3096
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,260✔
3097
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,260✔
3098
                    T_VARIABLE                 => T_VARIABLE,
1,260✔
3099
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,260✔
3100
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,260✔
3101
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,260✔
3102
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,260✔
3103
                ];
840✔
3104
                $allowed     += Tokens::MAGIC_CONSTANTS;
1,260✔
3105

3106
                for ($x = ($i - 1); $x >= 0; $x--) {
1,260✔
3107
                    // Allow for PHP 8.4 anon class dereferencing without wrapping parentheses.
3108
                    // Note: the T_CLASS token has not yet been retokenized to T_ANON_CLASS!
3109
                    if ($this->tokens[$x]['code'] === T_CLOSE_CURLY_BRACKET
1,260✔
3110
                        && isset($this->tokens[$x]['scope_condition']) === true
1,260✔
3111
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_CLASS
1,260✔
3112
                    ) {
3113
                        // Now, make sure it is an anonymous class and not a normal class.
3114
                        for ($y = ($this->tokens[$x]['scope_condition'] + 1); $y < $numTokens; $y++) {
123✔
3115
                            if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false) {
123✔
3116
                                break;
123✔
3117
                            }
3118
                        }
3119

3120
                        // Use the same check as used for the anon class retokenization.
3121
                        if ($this->tokens[$y]['code'] === T_OPEN_PARENTHESIS
123✔
3122
                            || $this->tokens[$y]['code'] === T_OPEN_CURLY_BRACKET
123✔
3123
                            || $this->tokens[$y]['code'] === T_EXTENDS
123✔
3124
                            || $this->tokens[$y]['code'] === T_IMPLEMENTS
123✔
3125
                        ) {
3126
                            break;
123✔
3127
                        }
3128
                    }
3129

3130
                    // If we hit a scope opener, the statement has ended
3131
                    // without finding anything, so it's probably an array
3132
                    // using PHP 7.1 short list syntax.
3133
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,260✔
3134
                        $isShortArray = true;
258✔
3135
                        break;
258✔
3136
                    }
3137

3138
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
1,260✔
3139
                        // Allow for control structures without braces.
3140
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,260✔
3141
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,260✔
3142
                            && isset(Tokens::SCOPE_OPENERS[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
123✔
3143
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,260✔
3144
                        ) {
3145
                            $isShortArray = true;
1,260✔
3146
                        }
3147

3148
                        break;
1,260✔
3149
                    }
3150
                }
3151

3152
                if ($isShortArray === true) {
1,260✔
3153
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,260✔
3154
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,260✔
3155

3156
                    $closer = $this->tokens[$i]['bracket_closer'];
1,260✔
3157
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,260✔
3158
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,260✔
3159
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,260✔
3160
                        $line = $this->tokens[$i]['line'];
×
3161
                        StatusWriter::write("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
3162
                        $line = $this->tokens[$closer]['line'];
×
3163
                        StatusWriter::write("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
3164
                    }
3165
                }
3166

3167
                continue;
1,260✔
3168
            } elseif ($this->tokens[$i]['code'] === T_MATCH) {
1,944✔
3169
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
819✔
3170
                    // Not a match expression after all.
3171
                    $this->tokens[$i]['code'] = T_STRING;
102✔
3172
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
3173

3174
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3175
                        StatusWriter::write("* token $i changed from T_MATCH to T_STRING", 2);
×
3176
                    }
3177

3178
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
3179
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
3180
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
3181
                        unset(
68✔
3182
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
3183
                            $this->tokens[$closer]['parenthesis_owner']
102✔
3184
                        );
68✔
3185
                        unset(
68✔
3186
                            $this->tokens[$i]['parenthesis_opener'],
102✔
3187
                            $this->tokens[$i]['parenthesis_closer'],
102✔
3188
                            $this->tokens[$i]['parenthesis_owner']
102✔
3189
                        );
68✔
3190

3191
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3192
                            StatusWriter::write("* cleaned parenthesis of token $i *", 2);
34✔
3193
                        }
3194
                    }
3195
                } else {
3196
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3197
                    $searchFor  = [
546✔
3198
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
819✔
3199
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
819✔
3200
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
819✔
3201
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
819✔
3202
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
819✔
3203
                    ];
546✔
3204
                    $searchFor += Tokens::SCOPE_OPENERS;
819✔
3205

3206
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
819✔
3207
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
819✔
3208
                            continue;
819✔
3209
                        }
3210

3211
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
492✔
3212
                            $x = $this->tokens[$x]['scope_closer'];
291✔
3213
                            continue;
291✔
3214
                        }
3215

3216
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
492✔
3217
                            $x = $this->tokens[$x]['parenthesis_closer'];
492✔
3218
                            continue;
492✔
3219
                        }
3220

3221
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
492✔
3222
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
3223
                            continue;
291✔
3224
                        }
3225

3226
                        // This must be a double arrow, but make sure anyhow.
3227
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
492✔
3228
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
492✔
3229
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
492✔
3230

3231
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
492✔
3232
                                StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
3233
                            }
3234
                        }
3235
                    }
3236
                }
3237

3238
                continue;
819✔
3239
            } elseif ($this->tokens[$i]['code'] === T_BITWISE_OR
1,944✔
3240
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,944✔
3241
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,944✔
3242
            ) {
3243
                if ($lastSeenTypeToken < $i) {
1,941✔
3244
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3245
                    // No need to do it again.
3246
                    continue;
933✔
3247
                }
3248

3249
                /*
3250
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3251
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3252
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3253

3254
                    All type related tokens will be converted in one go as soon as this section is hit.
3255
                */
3256

3257
                $allowed  = Tokens::NAME_TOKENS;
1,941✔
3258
                $allowed += [
1,294✔
3259
                    T_CALLABLE => T_CALLABLE,
1,941✔
3260
                    T_SELF     => T_SELF,
1,941✔
3261
                    T_PARENT   => T_PARENT,
1,941✔
3262
                    T_STATIC   => T_STATIC,
1,941✔
3263
                    T_FALSE    => T_FALSE,
1,941✔
3264
                    T_TRUE     => T_TRUE,
1,941✔
3265
                    T_NULL     => T_NULL,
1,941✔
3266
                ];
1,294✔
3267

3268
                $suspectedType       = null;
1,941✔
3269
                $typeTokenCountAfter = 0;
1,941✔
3270

3271
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,941✔
3272
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
1,941✔
3273
                        continue;
1,941✔
3274
                    }
3275

3276
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,941✔
3277
                        ++$typeTokenCountAfter;
1,488✔
3278
                        continue;
1,488✔
3279
                    }
3280

3281
                    if (($typeTokenCountAfter > 0
1,941✔
3282
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,941✔
3283
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,941✔
3284
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,941✔
3285
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,941✔
3286
                    ) {
3287
                        // Skip past reference and variadic indicators for parameter types.
3288
                        continue;
996✔
3289
                    }
3290

3291
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,941✔
3292
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3293
                        $suspectedType = 'property or parameter';
1,311✔
3294
                        break;
1,311✔
3295
                    }
3296

3297
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,941✔
3298
                        // Possible arrow function.
3299
                        $suspectedType = 'return';
1,233✔
3300
                        break;
1,233✔
3301
                    }
3302

3303
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,941✔
3304
                        // Possible abstract method or interface method.
3305
                        $suspectedType = 'return';
1,752✔
3306
                        break;
1,752✔
3307
                    }
3308

3309
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,941✔
3310
                        && isset($this->tokens[$x]['scope_condition']) === true
1,941✔
3311
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,941✔
3312
                    ) {
3313
                        $suspectedType = 'return';
1,629✔
3314
                        break;
1,629✔
3315
                    }
3316

3317
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,827✔
3318
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3319
                        $suspectedType = 'constant';
1,143✔
3320
                        break;
1,143✔
3321
                    }
3322

3323
                    break;
1,827✔
3324
                }
3325

3326
                if (($typeTokenCountAfter === 0
1,941✔
3327
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,941✔
3328
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,941✔
3329
                    || isset($suspectedType) === false
1,941✔
3330
                ) {
3331
                    // Definitely not a union, intersection or DNF type, move on.
3332
                    continue;
1,941✔
3333
                }
3334

3335
                if ($suspectedType === 'property or parameter') {
1,902✔
3336
                    unset($allowed[T_STATIC]);
1,311✔
3337
                }
3338

3339
                $typeTokenCountBefore = 0;
1,902✔
3340
                $typeOperators        = [$i];
1,902✔
3341
                $parenthesesCount     = 0;
1,902✔
3342
                $confirmed            = false;
1,902✔
3343
                $maybeNullable        = null;
1,902✔
3344

3345
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,902✔
3346
                    ++$parenthesesCount;
1,902✔
3347
                }
3348

3349
                for ($x = ($i - 1); $x >= 0; $x--) {
1,902✔
3350
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
1,902✔
3351
                        continue;
1,590✔
3352
                    }
3353

3354
                    if ($suspectedType === 'property or parameter'
1,902✔
3355
                        && $this->tokens[$x]['code'] === T_STRING
1,902✔
3356
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,902✔
3357
                    ) {
3358
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3359
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3360
                        $this->tokens[$x]['code'] = T_STATIC;
327✔
3361
                        $this->tokens[$x]['type'] = 'T_STATIC';
327✔
3362

3363
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
327✔
3364
                            $line = $this->tokens[$x]['line'];
×
3365
                            StatusWriter::write("* token $x on line $line changed back from T_STRING to T_STATIC", 1);
×
3366
                        }
3367
                    }
3368

3369
                    if ($suspectedType === 'property or parameter'
1,902✔
3370
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,902✔
3371
                    ) {
3372
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3373
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3374
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,311✔
3375
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,311✔
3376
                        ) {
3377
                            $confirmed = true;
1,197✔
3378
                            break;
1,197✔
3379
                        } else {
3380
                            // This may still be an arrow function which hasn't been handled yet.
3381
                            for ($y = ($x - 1); $y > 0; $y--) {
1,311✔
3382
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false
1,311✔
3383
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,311✔
3384
                                ) {
3385
                                    // Non-whitespace content.
3386
                                    break;
1,311✔
3387
                                }
3388
                            }
3389

3390
                            if ($this->tokens[$y]['code'] === T_FN) {
1,311✔
3391
                                $confirmed = true;
996✔
3392
                                break;
996✔
3393
                            }
3394
                        }
3395
                    }
3396

3397
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,902✔
3398
                        ++$typeTokenCountBefore;
1,590✔
3399
                        continue;
1,590✔
3400
                    }
3401

3402
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3403
                    if (($typeTokenCountBefore > 0
1,902✔
3404
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,902✔
3405
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,798✔
3406
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,902✔
3407
                    ) {
3408
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
972✔
3409
                            $maybeNullable = $x;
327✔
3410
                        }
3411

3412
                        continue;
972✔
3413
                    }
3414

3415
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,902✔
3416
                        $typeOperators[] = $x;
1,374✔
3417
                        continue;
1,374✔
3418
                    }
3419

3420
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,902✔
3421
                        ++$parenthesesCount;
1,590✔
3422
                        $typeOperators[] = $x;
1,590✔
3423
                        continue;
1,590✔
3424
                    }
3425

3426
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,902✔
3427
                        // Make sure this is the colon for a return type.
3428
                        for ($y = ($x - 1); $y > 0; $y--) {
1,032✔
3429
                            if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false) {
1,032✔
3430
                                break;
1,032✔
3431
                            }
3432
                        }
3433

3434
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
1,032✔
3435
                            // Definitely not a union, intersection or DNF return type, move on.
3436
                            continue 2;
327✔
3437
                        }
3438

3439
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
1,032✔
3440
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
996✔
3441
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
327✔
3442
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
327✔
3443
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_USE
996✔
3444
                            ) {
3445
                                $confirmed = true;
996✔
3446
                            }
3447

3448
                            break;
996✔
3449
                        }
3450

3451
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3452
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
1,032✔
3453
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
996✔
3454
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$z]['code']]) === false) {
996✔
3455
                                    break;
996✔
3456
                                }
3457
                            }
3458

3459
                            if ($this->tokens[$z]['code'] === T_FN) {
996✔
3460
                                $confirmed = true;
996✔
3461
                            }
3462
                        }
3463

3464
                        break;
1,032✔
3465
                    }
3466

3467
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,902✔
3468
                        $confirmed = true;
1,008✔
3469
                        break;
1,008✔
3470
                    }
3471

3472
                    if ($suspectedType === 'property or parameter'
1,902✔
3473
                        && (isset(Tokens::SCOPE_MODIFIERS[$this->tokens[$x]['code']]) === true
1,638✔
3474
                        || $this->tokens[$x]['code'] === T_VAR
1,588✔
3475
                        || $this->tokens[$x]['code'] === T_STATIC
1,588✔
3476
                        || $this->tokens[$x]['code'] === T_READONLY
1,588✔
3477
                        || $this->tokens[$x]['code'] === T_FINAL
1,533✔
3478
                        || $this->tokens[$x]['code'] === T_ABSTRACT)
1,902✔
3479
                    ) {
3480
                        // This will also confirm constructor property promotion parameters, but that's fine.
3481
                        $confirmed = true;
1,110✔
3482
                    }
3483

3484
                    break;
1,902✔
3485
                }
3486

3487
                // Remember the last token we examined as part of the (non-)"type declaration".
3488
                $lastSeenTypeToken = $x;
1,902✔
3489

3490
                if ($confirmed === false
1,902✔
3491
                    && $suspectedType === 'property or parameter'
1,902✔
3492
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,902✔
3493
                ) {
3494
                    $parens = $this->tokens[$i]['nested_parenthesis'];
795✔
3495
                    $last   = end($parens);
795✔
3496

3497
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
795✔
3498
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
795✔
3499
                    ) {
3500
                        $confirmed = true;
795✔
3501
                    } else {
3502
                        // No parenthesis owner set, this may be an arrow function which has not yet
3503
                        // had additional processing done.
3504
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
243✔
3505
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
243✔
3506
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
243✔
3507
                                    continue;
243✔
3508
                                }
3509

3510
                                break;
243✔
3511
                            }
3512

3513
                            if ($this->tokens[$x]['code'] === T_FN) {
243✔
3514
                                for (--$x; $x >= 0; $x--) {
×
3515
                                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true
×
3516
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3517
                                    ) {
3518
                                        continue;
×
3519
                                    }
3520

3521
                                    break;
×
3522
                                }
3523

3524
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3525
                                    $confirmed = true;
×
3526
                                }
3527
                            }
3528
                        }
3529
                    }
3530

3531
                    unset($parens, $last);
795✔
3532
                }
3533

3534
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,902✔
3535
                    // Not a (valid) union, intersection or DNF type after all, move on.
3536
                    continue;
1,788✔
3537
                }
3538

3539
                foreach ($typeOperators as $x) {
1,452✔
3540
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,452✔
3541
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,338✔
3542
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,338✔
3543

3544
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,338✔
3545
                            $line = $this->tokens[$x]['line'];
×
3546
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
446✔
3547
                        }
3548
                    } elseif ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,452✔
3549
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,338✔
3550
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,338✔
3551

3552
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,338✔
3553
                            $line = $this->tokens[$x]['line'];
×
3554
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
446✔
3555
                        }
3556
                    } elseif ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,452✔
3557
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,452✔
3558
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,452✔
3559

3560
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,452✔
3561
                            $line = $this->tokens[$x]['line'];
×
3562
                            StatusWriter::write("* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS", 1);
484✔
3563
                        }
3564
                    } elseif ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,452✔
3565
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,452✔
3566
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,452✔
3567

3568
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,452✔
3569
                            $line = $this->tokens[$x]['line'];
×
3570
                            StatusWriter::write("* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS", 1);
×
3571
                        }
3572
                    }
3573
                }
3574

3575
                if (isset($maybeNullable) === true) {
1,452✔
3576
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
327✔
3577
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
327✔
3578

3579
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
327✔
3580
                        $line = $this->tokens[$maybeNullable]['line'];
×
3581
                        StatusWriter::write("* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE", 1);
×
3582
                    }
3583
                }
3584

3585
                continue;
1,452✔
3586
            } elseif ($this->tokens[$i]['code'] === T_TRUE
1,944✔
3587
                || $this->tokens[$i]['code'] === T_FALSE
1,944✔
3588
                || $this->tokens[$i]['code'] === T_NULL
1,944✔
3589
            ) {
3590
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,746✔
3591
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
1,746✔
3592
                        // Non-whitespace content.
3593
                        break;
1,746✔
3594
                    }
3595
                }
3596

3597
                if ($x !== $numTokens
1,746✔
3598
                    && isset(static::T_STRING_CONTEXTS[$this->tokens[$x]['code']]) === true
1,746✔
3599
                ) {
3600
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3601
                        $line = $this->tokens[$i]['line'];
×
3602
                        $type = $this->tokens[$i]['type'];
×
3603
                        StatusWriter::write("* token $i on line $line changed from $type to T_STRING", 1);
×
3604
                    }
3605

3606
                    $this->tokens[$i]['code'] = T_STRING;
×
3607
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3608
                }
3609
            }
3610

3611
            if (($this->tokens[$i]['code'] !== T_CASE
1,944✔
3612
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,944✔
3613
                || isset($this->tokens[$i]['scope_opener']) === false
1,944✔
3614
            ) {
3615
                // Only interested in CASE and DEFAULT statements from here on in.
3616
                continue;
1,944✔
3617
            }
3618

3619
            $scopeOpener = $this->tokens[$i]['scope_opener'];
483✔
3620
            $scopeCloser = $this->tokens[$i]['scope_closer'];
483✔
3621

3622
            // If the first char after the opener is a curly brace
3623
            // and that brace has been ignored, it is actually
3624
            // opening this case statement and the opener and closer are
3625
            // probably set incorrectly.
3626
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
483✔
3627
                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
483✔
3628
                    // Non-whitespace content.
3629
                    break;
483✔
3630
                }
3631
            }
3632

3633
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
483✔
3634
                // Special case for multiple CASE statements that share the same
3635
                // closer. Because we are going backwards through the file, this next
3636
                // CASE statement is already fixed, so just use its closer and don't
3637
                // worry about fixing anything.
3638
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3639
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3640
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3641
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3642
                    $newType = $this->tokens[$newCloser]['type'];
×
3643
                    $line    = $this->tokens[$i]['line'];
×
3644
                    StatusWriter::write("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3645
                }
3646

3647
                continue;
×
3648
            }
3649

3650
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
483✔
3651
                || isset($this->tokens[$x]['scope_condition']) === true
483✔
3652
            ) {
3653
                // Not a CASE/DEFAULT with a curly brace opener.
3654
                continue;
483✔
3655
            }
3656

3657
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3658
            // not whatever it already is. The opener needs to be the opening curly
3659
            // brace so everything matches up.
3660
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3661
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3662
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3663
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3664
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3665
            }
3666

3667
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3668
                $line      = $this->tokens[$i]['line'];
×
3669
                $tokenType = $this->tokens[$i]['type'];
×
3670

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

3675
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3676
                $newType = $this->tokens[$newCloser]['type'];
×
3677
                StatusWriter::write("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3678
            }
3679

3680
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3681
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3682
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3683
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3684
            }
3685

3686
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3687
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3688
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3689
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3690
            } else {
3691
                // We were using a shared closer. All tokens that were
3692
                // sharing this closer with us, except for the scope condition
3693
                // and it's opener, need to now point to the new closer.
3694
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3695
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3696
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3697
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3698
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3699
                    ) {
3700
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3701

3702
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3703
                            $line      = $this->tokens[$y]['line'];
×
3704
                            $tokenType = $this->tokens[$y]['type'];
×
3705
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3706
                            $newType   = $this->tokens[$newCloser]['type'];
×
3707
                            StatusWriter::write("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3708
                        }
3709
                    }
3710
                }
3711
            }
3712

3713
            unset($this->tokens[$x]['bracket_opener']);
54✔
3714
            unset($this->tokens[$x]['bracket_closer']);
54✔
3715
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3716
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3717
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3718

3719
            // Now fix up all the tokens that think they are
3720
            // inside the CASE/DEFAULT statement when they are really outside.
3721
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3722
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3723
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3724
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3725
                        unset($this->tokens[$x]['conditions'][$num]);
×
3726

3727
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3728
                            $type     = $this->tokens[$x]['type'];
×
3729
                            $oldConds = '';
×
3730
                            foreach ($oldConditions as $condition) {
×
3731
                                $oldConds .= Tokens::tokenName($condition) . ',';
×
3732
                            }
3733

3734
                            $oldConds = rtrim($oldConds, ',');
×
3735

3736
                            $newConds = '';
×
3737
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3738
                                $newConds .= Tokens::tokenName($condition) . ',';
×
3739
                            }
3740

3741
                            $newConds = rtrim($newConds, ',');
×
3742

3743
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
3744
                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", 3);
×
3745
                        }
3746

3747
                        break;
×
3748
                    }
3749
                }
3750
            }
3751
        }
3752

3753
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,944✔
3754
            StatusWriter::write('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3755
        }
3756
    }
648✔
3757

3758

3759
    /**
3760
     * Takes a token produced from <code>token_get_all()</code> and produces a
3761
     * more uniform token.
3762
     *
3763
     * @param string|array $token The token to convert.
3764
     *
3765
     * @return array The new token.
3766
     */
3767
    public static function standardiseToken($token)
1✔
3768
    {
3769
        if (isset($token[1]) === false) {
1✔
3770
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
1✔
3771
                return self::$resolveTokenCache[$token[0]];
1✔
3772
            }
3773
        } else {
3774
            $cacheKey = null;
1✔
3775
            if ($token[0] === T_STRING) {
1✔
3776
                $cacheKey = strtolower($token[1]);
1✔
3777
            } elseif ($token[0] !== T_CURLY_OPEN) {
1✔
3778
                $cacheKey = $token[0];
1✔
3779
            }
3780

3781
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
1✔
3782
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3783
                $newToken['content'] = $token[1];
×
3784
                return $newToken;
×
3785
            }
3786
        }
3787

3788
        if (isset($token[1]) === false) {
1✔
3789
            return self::resolveSimpleToken($token[0]);
1✔
3790
        }
3791

3792
        if ($token[0] === T_STRING) {
1✔
3793
            switch ($cacheKey) {
1✔
3794
                case 'false':
1✔
3795
                    $newToken['type'] = 'T_FALSE';
1✔
3796
                    break;
1✔
3797
                case 'true':
1✔
3798
                    $newToken['type'] = 'T_TRUE';
1✔
3799
                    break;
1✔
3800
                case 'null':
1✔
3801
                    $newToken['type'] = 'T_NULL';
1✔
3802
                    break;
1✔
3803
                case 'self':
1✔
3804
                    $newToken['type'] = 'T_SELF';
1✔
3805
                    break;
1✔
3806
                case 'parent':
1✔
3807
                    $newToken['type'] = 'T_PARENT';
1✔
3808
                    break;
1✔
3809
                default:
3810
                    $newToken['type'] = 'T_STRING';
1✔
3811
                    break;
1✔
3812
            }
3813

3814
            $newToken['code'] = constant($newToken['type']);
1✔
3815

3816
            self::$resolveTokenCache[$cacheKey] = $newToken;
1✔
3817
        } elseif ($token[0] === T_CURLY_OPEN) {
1✔
3818
            $newToken = [
3819
                'code' => T_OPEN_CURLY_BRACKET,
×
3820
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3821
            ];
3822
        } else {
3823
            $newToken = [
3824
                'code' => $token[0],
1✔
3825
                'type' => Tokens::tokenName($token[0]),
1✔
3826
            ];
3827

3828
            self::$resolveTokenCache[$token[0]] = $newToken;
1✔
3829
        }
3830

3831
        $newToken['content'] = $token[1];
1✔
3832
        return $newToken;
1✔
3833
    }
3834

3835

3836
    /**
3837
     * Converts simple tokens into a format that conforms to complex tokens
3838
     * produced by token_get_all().
3839
     *
3840
     * Simple tokens are tokens that are not in array form when produced from
3841
     * token_get_all().
3842
     *
3843
     * @param string $token The simple token to convert.
3844
     *
3845
     * @return array The new token in array format.
3846
     */
3847
    public static function resolveSimpleToken(string $token)
1✔
3848
    {
3849
        $newToken = [];
1✔
3850

3851
        switch ($token) {
1✔
3852
            case '{':
1✔
3853
                $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1✔
3854
                break;
1✔
3855
            case '}':
1✔
3856
                $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1✔
3857
                break;
1✔
3858
            case '[':
1✔
3859
                $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1✔
3860
                break;
1✔
3861
            case ']':
1✔
3862
                $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1✔
3863
                break;
1✔
3864
            case '(':
1✔
3865
                $newToken['type'] = 'T_OPEN_PARENTHESIS';
1✔
3866
                break;
1✔
3867
            case ')':
1✔
3868
                $newToken['type'] = 'T_CLOSE_PARENTHESIS';
1✔
3869
                break;
1✔
3870
            case ':':
1✔
3871
                $newToken['type'] = 'T_COLON';
1✔
3872
                break;
1✔
3873
            case '.':
1✔
3874
                $newToken['type'] = 'T_STRING_CONCAT';
1✔
3875
                break;
1✔
3876
            case ';':
1✔
3877
                $newToken['type'] = 'T_SEMICOLON';
1✔
3878
                break;
1✔
3879
            case '=':
1✔
3880
                $newToken['type'] = 'T_EQUAL';
1✔
3881
                break;
1✔
3882
            case '*':
1✔
3883
                $newToken['type'] = 'T_MULTIPLY';
1✔
3884
                break;
1✔
3885
            case '/':
1✔
3886
                $newToken['type'] = 'T_DIVIDE';
1✔
3887
                break;
1✔
3888
            case '+':
1✔
3889
                $newToken['type'] = 'T_PLUS';
1✔
3890
                break;
1✔
3891
            case '-':
1✔
3892
                $newToken['type'] = 'T_MINUS';
1✔
3893
                break;
1✔
3894
            case '%':
1✔
3895
                $newToken['type'] = 'T_MODULUS';
1✔
3896
                break;
1✔
3897
            case '^':
1✔
3898
                $newToken['type'] = 'T_BITWISE_XOR';
1✔
3899
                break;
1✔
3900
            case '&':
1✔
3901
                $newToken['type'] = 'T_BITWISE_AND';
1✔
3902
                break;
1✔
3903
            case '|':
1✔
3904
                $newToken['type'] = 'T_BITWISE_OR';
1✔
3905
                break;
1✔
3906
            case '~':
1✔
3907
                $newToken['type'] = 'T_BITWISE_NOT';
1✔
3908
                break;
1✔
3909
            case '<':
1✔
3910
                $newToken['type'] = 'T_LESS_THAN';
1✔
3911
                break;
1✔
3912
            case '>':
1✔
3913
                $newToken['type'] = 'T_GREATER_THAN';
1✔
3914
                break;
1✔
3915
            case '!':
1✔
3916
                $newToken['type'] = 'T_BOOLEAN_NOT';
1✔
3917
                break;
1✔
3918
            case ',':
1✔
3919
                $newToken['type'] = 'T_COMMA';
1✔
3920
                break;
1✔
3921
            case '@':
1✔
3922
                $newToken['type'] = 'T_ASPERAND';
1✔
3923
                break;
1✔
3924
            case '$':
1✔
3925
                $newToken['type'] = 'T_DOLLAR';
1✔
3926
                break;
1✔
3927
            case '`':
1✔
3928
                $newToken['type'] = 'T_BACKTICK';
1✔
3929
                break;
1✔
3930
            default:
NEW
3931
                $newToken['type'] = 'T_NONE';
×
NEW
3932
                break;
×
3933
        }
3934

3935
        $newToken['code']    = constant($newToken['type']);
1✔
3936
        $newToken['content'] = $token;
1✔
3937

3938
        self::$resolveTokenCache[$token] = $newToken;
1✔
3939
        return $newToken;
1✔
3940
    }
3941

3942

3943
    /**
3944
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3945
     * Handle parenthesis balancing while searching for closing token
3946
     *
3947
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3948
     * @param int             $start        The starting position.
3949
     * @param string|string[] $openerTokens The opening character.
3950
     * @param string          $closerChar   The closing character.
3951
     *
3952
     * @return int|null The position of the closing token, if found. NULL otherwise.
3953
     */
3954
    private function findCloser(array &$tokens, int $start, $openerTokens, string $closerChar)
51✔
3955
    {
3956
        $numTokens    = count($tokens);
51✔
3957
        $stack        = [0];
51✔
3958
        $closer       = null;
51✔
3959
        $openerTokens = (array) $openerTokens;
51✔
3960

3961
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3962
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3963
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3964
            ) {
3965
                $stack[] = $x;
51✔
3966
            } elseif ($tokens[$x] === $closerChar) {
51✔
3967
                array_pop($stack);
51✔
3968
                if (empty($stack) === true) {
51✔
3969
                    $closer = $x;
51✔
3970
                    break;
51✔
3971
                }
3972
            }
3973
        }
3974

3975
        return $closer;
51✔
3976
    }
3977

3978

3979
    /**
3980
     * PHP 8 attributes parser for PHP < 8
3981
     * Handles single-line and multiline attributes.
3982
     *
3983
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3984
     * @param int   $stackPtr The current position in token array.
3985
     *
3986
     * @return array|null The array of parsed attribute tokens
3987
     */
3988
    private function parsePhpAttribute(array &$tokens, int $stackPtr)
19✔
3989
    {
3990

3991
        $token = $tokens[$stackPtr];
19✔
3992

3993
        $commentBody = substr($token[1], 2);
19✔
3994
        $subTokens   = @token_get_all('<?php ' . $commentBody);
19✔
3995

3996
        foreach ($subTokens as $i => $subToken) {
19✔
3997
            if (is_array($subToken) === true
19✔
3998
                && $subToken[0] === T_COMMENT
19✔
3999
                && strpos($subToken[1], '#[') === 0
19✔
4000
            ) {
4001
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
19✔
4002
                if ($reparsed !== null) {
19✔
4003
                    array_splice($subTokens, $i, 1, $reparsed);
19✔
4004
                } else {
4005
                    $subToken[0] = T_ATTRIBUTE;
×
4006
                }
4007
            }
4008
        }
4009

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

4012
        // Go looking for the close bracket.
4013
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
4014
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
19✔
4015
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
19✔
4016
                if (is_array($token) === true) {
19✔
4017
                    $commentBody .= $token[1];
19✔
4018
                } else {
4019
                    $commentBody .= $token;
19✔
4020
                }
4021
            }
4022

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

4026
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
4027
            if ($bracketCloser !== null) {
19✔
4028
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
19✔
4029
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
19✔
4030
            }
4031
        }
4032

4033
        if ($bracketCloser === null) {
19✔
4034
            return null;
19✔
4035
        }
4036

4037
        return $subTokens;
19✔
4038
    }
4039

4040

4041
    /**
4042
     * Creates a map for the attributes tokens that surround other tokens.
4043
     *
4044
     * @return void
4045
     */
4046
    private function createAttributesNestingMap()
3✔
4047
    {
4048
        $map = [];
3✔
4049
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
4050
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
4051
                && $i === $this->tokens[$i]['attribute_opener']
3✔
4052
            ) {
4053
                if (empty($map) === false) {
3✔
4054
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4055
                }
4056

4057
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
4058
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
4059
                        = $this->tokens[$i]['attribute_closer'];
3✔
4060
                }
4061
            } elseif (isset($this->tokens[$i]['attribute_closer']) === true
3✔
4062
                && $i === $this->tokens[$i]['attribute_closer']
3✔
4063
            ) {
4064
                array_pop($map);
3✔
4065
                if (empty($map) === false) {
3✔
4066
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4067
                }
4068
            } else {
4069
                if (empty($map) === false) {
3✔
4070
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4071
                }
4072
            }
4073
        }
4074
    }
1✔
4075
}
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