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

PHPCSStandards / PHP_CodeSniffer / 15253296250

26 May 2025 11:55AM UTC coverage: 78.632% (+0.3%) from 78.375%
15253296250

Pull #1105

github

web-flow
Merge d9441d98f into caf806050
Pull Request #1105: Skip tests when 'git' command is not available

19665 of 25009 relevant lines covered (78.63%)

88.67 hits per line

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

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

10
namespace PHP_CodeSniffer\Tokenizers;
11

12
use PHP_CodeSniffer\Util\Common;
13
use PHP_CodeSniffer\Util\Tokens;
14
use PHP_CodeSniffer\Util\Writers\StatusWriter;
15

16
class PHP extends Tokenizer
17
{
18

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

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

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

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

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

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

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

527

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

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

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

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

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

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

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

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

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

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

591
            if ($newStackPtr > 0
3,354✔
592
                && isset(Tokens::EMPTY_TOKENS[$finalTokens[($newStackPtr - 1)]['code']]) === false
3,354✔
593
            ) {
594
                $lastNotEmptyToken = ($newStackPtr - 1);
3,354✔
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,354✔
605
                if (isset($tokens[($stackPtr + 1)]) === true
×
606
                    && is_array($tokens[($stackPtr + 1)]) === true
×
607
                    && $tokens[($stackPtr + 1)][1][0] === "\n"
×
608
                ) {
609
                    $token[1] .= "\n";
×
610
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
611
                        if ($isWin === true) {
×
612
                            StatusWriter::write('\n', 0, 0);
×
613
                        } else {
614
                            StatusWriter::write("\033[30;1m\\n\033[0m", 0, 0);
×
615
                        }
616
                    }
617

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

628
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,354✔
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,354✔
637
                && isset(Tokens::CONTEXT_SENSITIVE_KEYWORDS[$token[0]]) === true
3,354✔
638
                && (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
3,254✔
639
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
3,254✔
640
                || $insideConstDeclaration === true)
3,354✔
641
            ) {
642
                if (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,615✔
643
                    $preserveKeyword = false;
2,615✔
644

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

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

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

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

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

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

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

692
                            break;
113✔
693
                        }
694
                    }
695
                }//end if
696

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

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

712
                        break;
2,264✔
713
                    }
714

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

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

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

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

733
                        break;
633✔
734
                    }
735
                }
736

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

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

749
                    $newStackPtr++;
1,246✔
750
                    continue;
1,246✔
751
                }
752
            }//end if
753

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

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

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

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

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

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

803
                    break;
2,058✔
804
                }
805
            }//end if
806

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

813
            if (PHP_VERSION_ID < 70400
3,354✔
814
                && $tokenIsArray === true
3,354✔
815
                // PHP < 7.4 with short open tags off.
816
                && (($stackPtr === ($numTokens - 1)
3,354✔
817
                && $token[0] === T_INLINE_HTML
3,354✔
818
                && stripos($token[1], '<?php') === 0)
2,259✔
819
                // PHP < 7.4 with short open tags on.
2,238✔
820
                || ($stackPtr === ($numTokens - 2)
3,354✔
821
                && $token[0] === T_OPEN_TAG
3,354✔
822
                && $token[1] === '<?'
3,354✔
823
                && is_array($tokens[($stackPtr + 1)]) === true
3,354✔
824
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,354✔
825
                && strtolower($tokens[($stackPtr + 1)][1]) === 'php'))
3,354✔
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
            }//end if
846

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

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

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

865
                if (isset($openTagAndWhiteSpace[1]) === true) {
3,354✔
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,354✔
871
                        && $openTagAndWhiteSpace[1] === ' '
3,354✔
872
                        && is_array($tokens[($stackPtr + 1)]) === true
3,354✔
873
                        && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,354✔
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,354✔
884
                            'code'    => T_WHITESPACE,
3,354✔
885
                            'type'    => 'T_WHITESPACE',
3,354✔
886
                            'content' => $openTagAndWhiteSpace[1],
3,354✔
887
                        ];
2,238✔
888

889
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,354✔
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,354✔
894
                    }//end if
895
                }//end if
896

897
                continue;
3,354✔
898
            }//end if
899

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

904
            if ($tokenIsArray === true
3,354✔
905
                && ($token[0] === T_DOC_COMMENT
3,354✔
906
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
3,354✔
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,354✔
919
                && $tokenIsArray === true
3,354✔
920
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
3,354✔
921
                && isset($tokens[($stackPtr + 1)]) === true
3,354✔
922
                && is_array($tokens[($stackPtr + 1)]) === true
3,354✔
923
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,354✔
924
            ) {
925
                $nextToken = $tokens[($stackPtr + 1)];
1,728✔
926

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

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

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

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

961
            if (PHP_VERSION_ID < 80100
3,354✔
962
                && $tokenIsArray === true && $token[1] === '0'
3,354✔
963
                && (isset($tokens[($stackPtr + 1)]) === true
2,814✔
964
                && is_array($tokens[($stackPtr + 1)]) === true
2,814✔
965
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,814✔
966
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,814✔
967
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,814✔
968
                && $tokens[($stackPtr + 1)][1][1] !== '_')
3,354✔
969
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
3,354✔
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
            }//end if
995

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

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

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

1022
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
3,354✔
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
                }//end for
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
            }//end if
1090

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

1095
            if ($tokenIsArray === true
3,354✔
1096
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
3,354✔
1097
                && (substr($token[1], 0, 2) === 'b"'
3,174✔
1098
                || substr($token[1], 0, 2) === "b'")
3,354✔
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,354✔
1110
                && $token[0] === T_STRING_CAST
3,354✔
1111
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
3,354✔
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,354✔
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
                }//end for
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
            }//end if
1220

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1359
                continue;
176✔
1360
            }//end if
1361

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

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

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

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

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

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

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

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

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

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

1444
                    continue;
472✔
1445
                }//end if
1446
            }//end if
1447

1448
            /*
1449
                PHP 8.0 Attributes
1450
            */
1451

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

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

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

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

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

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

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

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

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

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

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

1533
                        $newStackPtr++;
690✔
1534

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

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

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

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

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

1567
                $isReadonlyKeyword = false;
1,008✔
1568

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1666
                continue;
1,008✔
1667
            }//end if
1668

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1814
                continue;
×
1815
            }
1816

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

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

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

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

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

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

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

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

1889
                        continue;
27✔
1890
                    }//end if
1891

1892
                    break;
27✔
1893
                }//end for
1894

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2038
            if ($tokenIsArray === false && $token[0] === '?') {
3,354✔
2039
                $newToken            = [];
1,284✔
2040
                $newToken['content'] = '?';
1,284✔
2041

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

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

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

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

2063
                $lastRelevantNonEmpty = null;
1,284✔
2064

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

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

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

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

2098
                        $newToken['code'] = T_INLINE_THEN;
1,122✔
2099
                        $newToken['type'] = 'T_INLINE_THEN';
1,122✔
2100

2101
                        $insideInlineIf[] = $stackPtr;
1,122✔
2102

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

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

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

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

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

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

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

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

2146
                            break;
150✔
2147
                        }
2148

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2273
                            break;
2,616✔
2274
                        }
2275
                    }
2276

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

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

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

2303
                                continue;
150✔
2304
                            }
2305

2306
                            break;
1,971✔
2307
                        }//end for
2308
                    }//end if
2309
                }//end if
2310
            }//end if
2311

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

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

2333
                    break;
2,952✔
2334
                }
2335

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

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

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

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

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

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

2381
                        $newStackPtr++;
153✔
2382
                        continue;
153✔
2383
                    }//end if
2384
                }//end if
2385
            }//end if
2386

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

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

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

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

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

2435
                            break;
1,636✔
2436
                        }
2437

2438
                        if ($tokens[$i] === '=') {
1,636✔
2439
                            $preserveTstring        = true;
1,432✔
2440
                            $insideConstDeclaration = false;
1,500✔
2441
                        }
2442
                    } else if (isset(static::T_STRING_CONTEXTS[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,952✔
2443
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,952✔
2444
                    ) {
2445
                        $preserveTstring = true;
2,838✔
2446

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

2462
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,053✔
2463
                                $preserveTstring = true;
480✔
2464
                            }
2465

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

2477
                            if ($tokens[$i][0] === '(') {
2,712✔
2478
                                $preserveTstring = true;
2,280✔
2479
                            }
2480

2481
                            break;
2,712✔
2482
                        }
2483
                    }//end if
2484

2485
                    if ($preserveTstring === true) {
2,952✔
2486
                        $finalTokens[$newStackPtr] = [
2,841✔
2487
                            'code'    => T_STRING,
2,841✔
2488
                            'type'    => 'T_STRING',
2,841✔
2489
                            'content' => $token[1],
2,841✔
2490
                        ];
1,894✔
2491

2492
                        $newStackPtr++;
2,841✔
2493
                        continue;
2,841✔
2494
                    }
2495
                }//end if
2496

2497
                $newToken = null;
3,354✔
2498
                if ($tokenIsArray === false) {
3,354✔
2499
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3,342✔
2500
                        $newToken = self::$resolveTokenCache[$token[0]];
3,342✔
2501
                    }
2502
                } else {
2503
                    $cacheKey = null;
3,354✔
2504
                    if ($token[0] === T_STRING) {
3,354✔
2505
                        $cacheKey = strtolower($token[1]);
2,709✔
2506
                    } else if ($token[0] !== T_CURLY_OPEN) {
3,354✔
2507
                        $cacheKey = $token[0];
3,354✔
2508
                    }
2509

2510
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,354✔
2511
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,354✔
2512
                        $newToken['content'] = $token[1];
3,354✔
2513
                    }
2514
                }
2515

2516
                if ($newToken === null) {
3,354✔
2517
                    $newToken = self::standardiseToken($token);
17✔
2518
                }
2519

2520
                // Convert colons that are actually the ELSE component of an
2521
                // inline IF statement.
2522
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
3,354✔
2523
                    $isInlineIf = true;
1,122✔
2524

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

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

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

2554
                        if ($tokens[$i] === ')') {
1,122✔
2555
                            $parenCount = 1;
639✔
2556
                            for ($i--; $i > 0; $i--) {
639✔
2557
                                if ($tokens[$i] === '(') {
639✔
2558
                                    $parenCount--;
639✔
2559
                                    if ($parenCount === 0) {
639✔
2560
                                        break;
639✔
2561
                                    }
2562
                                } else if ($tokens[$i] === ')') {
639✔
2563
                                    $parenCount++;
×
2564
                                }
2565
                            }
2566

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

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

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

2604
                                break;
×
2605
                            }
2606

2607
                            if (is_array($tokens[$i]) === false
1,122✔
2608
                                && ($tokens[$i] === ';'
1,122✔
2609
                                || $tokens[$i] === '{'
1,122✔
2610
                                || $tokens[$i] === '}')
1,122✔
2611
                            ) {
2612
                                break;
822✔
2613
                            }
2614
                        }//end for
2615
                    }//end if
2616

2617
                    if ($isInlineIf === true) {
1,122✔
2618
                        array_pop($insideInlineIf);
1,122✔
2619
                        $newToken['code'] = T_INLINE_ELSE;
1,122✔
2620
                        $newToken['type'] = 'T_INLINE_ELSE';
1,122✔
2621

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

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

2643
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,524✔
2644
                        $newToken['code'] = T_STRING;
1,293✔
2645
                        $newToken['type'] = 'T_STRING';
1,293✔
2646
                    }
2647
                }
2648

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

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

2671
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,354✔
2672
                    $newToken['code'] = T_CLOSE_USE_GROUP;
150✔
2673
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
150✔
2674
                    $insideUseGroup   = false;
150✔
2675
                }
2676

2677
                $finalTokens[$newStackPtr] = $newToken;
3,354✔
2678
                $newStackPtr++;
3,354✔
2679
            }//end if
2680
        }//end for
2681

2682
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,354✔
2683
            StatusWriter::write('*** END PHP TOKENIZING ***', 1);
×
2684
        }
2685

2686
        return $finalTokens;
3,354✔
2687

2688
    }//end tokenize()
2689

2690

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

2709
        $this->createAttributesNestingMap();
1,908✔
2710

2711
        $numTokens         = count($this->tokens);
1,908✔
2712
        $lastSeenTypeToken = $numTokens;
1,908✔
2713

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

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

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

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

2745
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,131✔
2746
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
678✔
2747
                                continue;
×
2748
                            }
2749

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

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

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

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

2783
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,125✔
2784
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,125✔
2785
                    ) {
2786
                        $closer = $this->tokens[$x]['parenthesis_closer'];
672✔
2787

2788
                        $this->tokens[$i]['parenthesis_opener']     = $x;
672✔
2789
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
672✔
2790
                        $this->tokens[$i]['parenthesis_owner']      = $i;
672✔
2791
                        $this->tokens[$x]['parenthesis_owner']      = $i;
672✔
2792
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
672✔
2793

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

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

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

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

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

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

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

2868
                        $inTernary    = false;
1,212✔
2869
                        $lastEndToken = null;
1,212✔
2870

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

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

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

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

2912
                                break;
1,212✔
2913
                            }
2914

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

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

2935
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,212✔
2936
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
759✔
2937
                                $lastEndToken = $scopeCloser;
759✔
2938
                                continue;
759✔
2939
                            }
2940

2941
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,212✔
2942
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
201✔
2943
                                $lastEndToken = $scopeCloser;
201✔
2944
                                continue;
201✔
2945
                            }
2946

2947
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,212✔
2948
                                $inTernary = true;
201✔
2949
                                continue;
201✔
2950
                            }
2951

2952
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,212✔
2953
                                if ($inTernary === false) {
201✔
2954
                                    break;
201✔
2955
                                }
2956

2957
                                $inTernary = false;
201✔
2958
                                continue;
201✔
2959
                            }
2960
                        }//end for
2961

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

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

2981
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,212✔
2982
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,212✔
2983

2984
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,212✔
2985
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,212✔
2986
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,212✔
2987
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,212✔
2988
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,212✔
2989
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,212✔
2990

2991
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,212✔
2992
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,212✔
2993
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,212✔
2994
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,212✔
2995

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

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

3012
                    $this->tokens[$i]['code'] = T_STRING;
657✔
3013
                    $this->tokens[$i]['type'] = 'T_STRING';
843✔
3014
                }
3015
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,908✔
3016
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,230✔
3017
                    continue;
108✔
3018
                }
3019

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

3036
                for ($x = ($i - 1); $x >= 0; $x--) {
1,230✔
3037
                    // If we hit a scope opener, the statement has ended
3038
                    // without finding anything, so it's probably an array
3039
                    // using PHP 7.1 short list syntax.
3040
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,230✔
3041
                        $isShortArray = true;
243✔
3042
                        break;
243✔
3043
                    }
3044

3045
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
1,230✔
3046
                        // Allow for control structures without braces.
3047
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,230✔
3048
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,230✔
3049
                            && isset(Tokens::SCOPE_OPENERS[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
108✔
3050
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,230✔
3051
                        ) {
3052
                            $isShortArray = true;
1,230✔
3053
                        }
3054

3055
                        break;
1,230✔
3056
                    }
3057
                }//end for
3058

3059
                if ($isShortArray === true) {
1,230✔
3060
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,230✔
3061
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,230✔
3062

3063
                    $closer = $this->tokens[$i]['bracket_closer'];
1,230✔
3064
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,230✔
3065
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,230✔
3066
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,230✔
3067
                        $line = $this->tokens[$i]['line'];
×
3068
                        StatusWriter::write("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
3069
                        $line = $this->tokens[$closer]['line'];
×
3070
                        StatusWriter::write("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
3071
                    }
3072
                }
3073

3074
                continue;
1,230✔
3075
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,908✔
3076
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
813✔
3077
                    // Not a match expression after all.
3078
                    $this->tokens[$i]['code'] = T_STRING;
102✔
3079
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
3080

3081
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3082
                        StatusWriter::write("* token $i changed from T_MATCH to T_STRING", 2);
×
3083
                    }
3084

3085
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
3086
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
3087
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
3088
                        unset(
68✔
3089
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
3090
                            $this->tokens[$closer]['parenthesis_owner']
102✔
3091
                        );
68✔
3092
                        unset(
68✔
3093
                            $this->tokens[$i]['parenthesis_opener'],
102✔
3094
                            $this->tokens[$i]['parenthesis_closer'],
102✔
3095
                            $this->tokens[$i]['parenthesis_owner']
102✔
3096
                        );
68✔
3097

3098
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3099
                            StatusWriter::write("* cleaned parenthesis of token $i *", 2);
34✔
3100
                        }
3101
                    }
3102
                } else {
3103
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3104
                    $searchFor  = [
542✔
3105
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
813✔
3106
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
813✔
3107
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
813✔
3108
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
813✔
3109
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
813✔
3110
                    ];
542✔
3111
                    $searchFor += Tokens::SCOPE_OPENERS;
813✔
3112

3113
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
813✔
3114
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
813✔
3115
                            continue;
813✔
3116
                        }
3117

3118
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
492✔
3119
                            $x = $this->tokens[$x]['scope_closer'];
291✔
3120
                            continue;
291✔
3121
                        }
3122

3123
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
492✔
3124
                            $x = $this->tokens[$x]['parenthesis_closer'];
492✔
3125
                            continue;
492✔
3126
                        }
3127

3128
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
492✔
3129
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
3130
                            continue;
291✔
3131
                        }
3132

3133
                        // This must be a double arrow, but make sure anyhow.
3134
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
492✔
3135
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
492✔
3136
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
492✔
3137

3138
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
492✔
3139
                                StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
3140
                            }
3141
                        }
3142
                    }//end for
3143
                }//end if
3144

3145
                continue;
813✔
3146
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,908✔
3147
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,908✔
3148
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,908✔
3149
            ) {
3150
                if ($lastSeenTypeToken < $i) {
1,905✔
3151
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3152
                    // No need to do it again.
3153
                    continue;
912✔
3154
                }
3155

3156
                /*
3157
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3158
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3159
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3160

3161
                    All type related tokens will be converted in one go as soon as this section is hit.
3162
                */
3163

3164
                $allowed  = Tokens::NAME_TOKENS;
1,905✔
3165
                $allowed += [
1,270✔
3166
                    T_CALLABLE => T_CALLABLE,
1,905✔
3167
                    T_SELF     => T_SELF,
1,905✔
3168
                    T_PARENT   => T_PARENT,
1,905✔
3169
                    T_STATIC   => T_STATIC,
1,905✔
3170
                    T_FALSE    => T_FALSE,
1,905✔
3171
                    T_TRUE     => T_TRUE,
1,905✔
3172
                    T_NULL     => T_NULL,
1,905✔
3173
                ];
1,270✔
3174

3175
                $suspectedType       = null;
1,905✔
3176
                $typeTokenCountAfter = 0;
1,905✔
3177

3178
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,905✔
3179
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
1,905✔
3180
                        continue;
1,905✔
3181
                    }
3182

3183
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,905✔
3184
                        ++$typeTokenCountAfter;
1,467✔
3185
                        continue;
1,467✔
3186
                    }
3187

3188
                    if (($typeTokenCountAfter > 0
1,905✔
3189
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,905✔
3190
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,905✔
3191
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,905✔
3192
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,905✔
3193
                    ) {
3194
                        // Skip past reference and variadic indicators for parameter types.
3195
                        continue;
975✔
3196
                    }
3197

3198
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,905✔
3199
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3200
                        $suspectedType = 'property or parameter';
1,290✔
3201
                        break;
1,290✔
3202
                    }
3203

3204
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,905✔
3205
                        // Possible arrow function.
3206
                        $suspectedType = 'return';
1,212✔
3207
                        break;
1,212✔
3208
                    }
3209

3210
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,905✔
3211
                        // Possible abstract method or interface method.
3212
                        $suspectedType = 'return';
1,716✔
3213
                        break;
1,716✔
3214
                    }
3215

3216
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,905✔
3217
                        && isset($this->tokens[$x]['scope_condition']) === true
1,905✔
3218
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,905✔
3219
                    ) {
3220
                        $suspectedType = 'return';
1,608✔
3221
                        break;
1,608✔
3222
                    }
3223

3224
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,791✔
3225
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3226
                        $suspectedType = 'constant';
1,122✔
3227
                        break;
1,122✔
3228
                    }
3229

3230
                    break;
1,791✔
3231
                }//end for
3232

3233
                if (($typeTokenCountAfter === 0
1,905✔
3234
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,905✔
3235
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,905✔
3236
                    || isset($suspectedType) === false
1,905✔
3237
                ) {
3238
                    // Definitely not a union, intersection or DNF type, move on.
3239
                    continue;
1,905✔
3240
                }
3241

3242
                if ($suspectedType === 'property or parameter') {
1,866✔
3243
                    unset($allowed[T_STATIC]);
1,290✔
3244
                }
3245

3246
                $typeTokenCountBefore = 0;
1,866✔
3247
                $typeOperators        = [$i];
1,866✔
3248
                $parenthesesCount     = 0;
1,866✔
3249
                $confirmed            = false;
1,866✔
3250
                $maybeNullable        = null;
1,866✔
3251

3252
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,866✔
3253
                    ++$parenthesesCount;
1,866✔
3254
                }
3255

3256
                for ($x = ($i - 1); $x >= 0; $x--) {
1,866✔
3257
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
1,866✔
3258
                        continue;
1,569✔
3259
                    }
3260

3261
                    if ($suspectedType === 'property or parameter'
1,866✔
3262
                        && $this->tokens[$x]['code'] === T_STRING
1,866✔
3263
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,866✔
3264
                    ) {
3265
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3266
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3267
                        $this->tokens[$x]['code'] = T_STATIC;
321✔
3268
                        $this->tokens[$x]['type'] = 'T_STATIC';
321✔
3269

3270
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
321✔
3271
                            $line = $this->tokens[$x]['line'];
×
3272
                            StatusWriter::write("* token $x on line $line changed back from T_STRING to T_STATIC", 1);
×
3273
                        }
3274
                    }
3275

3276
                    if ($suspectedType === 'property or parameter'
1,866✔
3277
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,866✔
3278
                    ) {
3279
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3280
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3281
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,290✔
3282
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,290✔
3283
                        ) {
3284
                            $confirmed = true;
1,109✔
3285
                            break;
1,109✔
3286
                        } else {
3287
                            // This may still be an arrow function which hasn't been handled yet.
3288
                            for ($y = ($x - 1); $y > 0; $y--) {
1,290✔
3289
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false
1,290✔
3290
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,290✔
3291
                                ) {
3292
                                    // Non-whitespace content.
3293
                                    break;
1,290✔
3294
                                }
3295
                            }
3296

3297
                            if ($this->tokens[$y]['code'] === T_FN) {
1,290✔
3298
                                $confirmed = true;
975✔
3299
                                break;
975✔
3300
                            }
3301
                        }
3302
                    }//end if
3303

3304
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,866✔
3305
                        ++$typeTokenCountBefore;
1,569✔
3306
                        continue;
1,569✔
3307
                    }
3308

3309
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3310
                    if (($typeTokenCountBefore > 0
1,866✔
3311
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,866✔
3312
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,767✔
3313
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,866✔
3314
                    ) {
3315
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
951✔
3316
                            $maybeNullable = $x;
321✔
3317
                        }
3318

3319
                        continue;
951✔
3320
                    }
3321

3322
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,866✔
3323
                        $typeOperators[] = $x;
1,353✔
3324
                        continue;
1,353✔
3325
                    }
3326

3327
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,866✔
3328
                        ++$parenthesesCount;
1,569✔
3329
                        $typeOperators[] = $x;
1,569✔
3330
                        continue;
1,569✔
3331
                    }
3332

3333
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,866✔
3334
                        // Make sure this is the colon for a return type.
3335
                        for ($y = ($x - 1); $y > 0; $y--) {
1,011✔
3336
                            if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$y]['code']]) === false) {
1,011✔
3337
                                break;
1,011✔
3338
                            }
3339
                        }
3340

3341
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
1,011✔
3342
                            // Definitely not a union, intersection or DNF return type, move on.
3343
                            continue 2;
321✔
3344
                        }
3345

3346
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
1,011✔
3347
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
908✔
3348
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
321✔
3349
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
321✔
3350
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_USE
908✔
3351
                            ) {
3352
                                $confirmed = true;
908✔
3353
                            }
3354

3355
                            break;
908✔
3356
                        }
3357

3358
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3359
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
1,011✔
3360
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
975✔
3361
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$z]['code']]) === false) {
975✔
3362
                                    break;
975✔
3363
                                }
3364
                            }
3365

3366
                            if ($this->tokens[$z]['code'] === T_FN) {
975✔
3367
                                $confirmed = true;
975✔
3368
                            }
3369
                        }
3370

3371
                        break;
1,011✔
3372
                    }//end if
3373

3374
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,866✔
3375
                        $confirmed = true;
987✔
3376
                        break;
987✔
3377
                    }
3378

3379
                    if ($suspectedType === 'property or parameter'
1,866✔
3380
                        && (isset(Tokens::SCOPE_MODIFIERS[$this->tokens[$x]['code']]) === true
1,607✔
3381
                        || $this->tokens[$x]['code'] === T_VAR
1,557✔
3382
                        || $this->tokens[$x]['code'] === T_STATIC
1,557✔
3383
                        || $this->tokens[$x]['code'] === T_READONLY
1,557✔
3384
                        || $this->tokens[$x]['code'] === T_FINAL)
1,866✔
3385
                    ) {
3386
                        // This will also confirm constructor property promotion parameters, but that's fine.
3387
                        $confirmed = true;
1,089✔
3388
                    }
3389

3390
                    break;
1,866✔
3391
                }//end for
3392

3393
                // Remember the last token we examined as part of the (non-)"type declaration".
3394
                $lastSeenTypeToken = $x;
1,866✔
3395

3396
                if ($confirmed === false
1,866✔
3397
                    && $suspectedType === 'property or parameter'
1,866✔
3398
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,866✔
3399
                ) {
3400
                    $parens = $this->tokens[$i]['nested_parenthesis'];
774✔
3401
                    $last   = end($parens);
774✔
3402

3403
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
774✔
3404
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
774✔
3405
                    ) {
3406
                        $confirmed = true;
774✔
3407
                    } else {
3408
                        // No parenthesis owner set, this may be an arrow function which has not yet
3409
                        // had additional processing done.
3410
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
234✔
3411
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
234✔
3412
                                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
234✔
3413
                                    continue;
234✔
3414
                                }
3415

3416
                                break;
234✔
3417
                            }
3418

3419
                            if ($this->tokens[$x]['code'] === T_FN) {
234✔
3420
                                for (--$x; $x >= 0; $x--) {
×
3421
                                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true
×
3422
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3423
                                    ) {
3424
                                        continue;
×
3425
                                    }
3426

3427
                                    break;
×
3428
                                }
3429

3430
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3431
                                    $confirmed = true;
×
3432
                                }
3433
                            }
3434
                        }//end if
3435
                    }//end if
3436

3437
                    unset($parens, $last);
774✔
3438
                }//end if
3439

3440
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,866✔
3441
                    // Not a (valid) union, intersection or DNF type after all, move on.
3442
                    continue;
1,752✔
3443
                }
3444

3445
                foreach ($typeOperators as $x) {
1,431✔
3446
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,431✔
3447
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,317✔
3448
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,317✔
3449

3450
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,317✔
3451
                            $line = $this->tokens[$x]['line'];
×
3452
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
439✔
3453
                        }
3454
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,431✔
3455
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,317✔
3456
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,317✔
3457

3458
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,317✔
3459
                            $line = $this->tokens[$x]['line'];
×
3460
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
439✔
3461
                        }
3462
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,431✔
3463
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,431✔
3464
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,431✔
3465

3466
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,431✔
3467
                            $line = $this->tokens[$x]['line'];
×
3468
                            StatusWriter::write("* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS", 1);
477✔
3469
                        }
3470
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,431✔
3471
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,431✔
3472
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,431✔
3473

3474
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,431✔
3475
                            $line = $this->tokens[$x]['line'];
×
3476
                            StatusWriter::write("* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS", 1);
×
3477
                        }
3478
                    }//end if
3479
                }//end foreach
3480

3481
                if (isset($maybeNullable) === true) {
1,431✔
3482
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
321✔
3483
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
321✔
3484

3485
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
321✔
3486
                        $line = $this->tokens[$maybeNullable]['line'];
×
3487
                        StatusWriter::write("* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE", 1);
×
3488
                    }
3489
                }
3490

3491
                continue;
1,431✔
3492
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,908✔
3493
                || $this->tokens[$i]['code'] === T_FALSE
1,908✔
3494
                || $this->tokens[$i]['code'] === T_NULL
1,908✔
3495
            ) {
3496
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,710✔
3497
                    if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
1,710✔
3498
                        // Non-whitespace content.
3499
                        break;
1,710✔
3500
                    }
3501
                }
3502

3503
                if ($x !== $numTokens
1,710✔
3504
                    && isset(static::T_STRING_CONTEXTS[$this->tokens[$x]['code']]) === true
1,710✔
3505
                ) {
3506
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3507
                        $line = $this->tokens[$i]['line'];
×
3508
                        $type = $this->tokens[$i]['type'];
×
3509
                        StatusWriter::write("* token $i on line $line changed from $type to T_STRING", 1);
×
3510
                    }
3511

3512
                    $this->tokens[$i]['code'] = T_STRING;
×
3513
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3514
                }
3515
            }//end if
3516

3517
            if (($this->tokens[$i]['code'] !== T_CASE
1,908✔
3518
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,908✔
3519
                || isset($this->tokens[$i]['scope_opener']) === false
1,908✔
3520
            ) {
3521
                // Only interested in CASE and DEFAULT statements from here on in.
3522
                continue;
1,908✔
3523
            }
3524

3525
            $scopeOpener = $this->tokens[$i]['scope_opener'];
477✔
3526
            $scopeCloser = $this->tokens[$i]['scope_closer'];
477✔
3527

3528
            // If the first char after the opener is a curly brace
3529
            // and that brace has been ignored, it is actually
3530
            // opening this case statement and the opener and closer are
3531
            // probably set incorrectly.
3532
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
477✔
3533
                if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === false) {
477✔
3534
                    // Non-whitespace content.
3535
                    break;
477✔
3536
                }
3537
            }
3538

3539
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
477✔
3540
                // Special case for multiple CASE statements that share the same
3541
                // closer. Because we are going backwards through the file, this next
3542
                // CASE statement is already fixed, so just use its closer and don't
3543
                // worry about fixing anything.
3544
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3545
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3546
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3547
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3548
                    $newType = $this->tokens[$newCloser]['type'];
×
3549
                    $line    = $this->tokens[$i]['line'];
×
3550
                    StatusWriter::write("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3551
                }
3552

3553
                continue;
×
3554
            }
3555

3556
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
477✔
3557
                || isset($this->tokens[$x]['scope_condition']) === true
477✔
3558
            ) {
3559
                // Not a CASE/DEFAULT with a curly brace opener.
3560
                continue;
477✔
3561
            }
3562

3563
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3564
            // not whatever it already is. The opener needs to be the opening curly
3565
            // brace so everything matches up.
3566
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3567
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3568
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3569
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3570
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3571
            }
3572

3573
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3574
                $line      = $this->tokens[$i]['line'];
×
3575
                $tokenType = $this->tokens[$i]['type'];
×
3576

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

3581
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3582
                $newType = $this->tokens[$newCloser]['type'];
×
3583
                StatusWriter::write("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3584
            }
3585

3586
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3587
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3588
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3589
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3590
            }
3591

3592
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3593
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3594
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3595
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3596
            } else {
3597
                // We were using a shared closer. All tokens that were
3598
                // sharing this closer with us, except for the scope condition
3599
                // and it's opener, need to now point to the new closer.
3600
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3601
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3602
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3603
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3604
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3605
                    ) {
3606
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3607

3608
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3609
                            $line      = $this->tokens[$y]['line'];
×
3610
                            $tokenType = $this->tokens[$y]['type'];
×
3611
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3612
                            $newType   = $this->tokens[$newCloser]['type'];
×
3613
                            StatusWriter::write("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3614
                        }
3615
                    }
3616
                }
3617
            }//end if
3618

3619
            unset($this->tokens[$x]['bracket_opener']);
54✔
3620
            unset($this->tokens[$x]['bracket_closer']);
54✔
3621
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3622
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3623
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3624

3625
            // Now fix up all the tokens that think they are
3626
            // inside the CASE/DEFAULT statement when they are really outside.
3627
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3628
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3629
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3630
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3631
                        unset($this->tokens[$x]['conditions'][$num]);
×
3632

3633
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3634
                            $type     = $this->tokens[$x]['type'];
×
3635
                            $oldConds = '';
×
3636
                            foreach ($oldConditions as $condition) {
×
3637
                                $oldConds .= Tokens::tokenName($condition).',';
×
3638
                            }
3639

3640
                            $oldConds = rtrim($oldConds, ',');
×
3641

3642
                            $newConds = '';
×
3643
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3644
                                $newConds .= Tokens::tokenName($condition).',';
×
3645
                            }
3646

3647
                            $newConds = rtrim($newConds, ',');
×
3648

3649
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
3650
                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", 3);
×
3651
                        }
3652

3653
                        break;
×
3654
                    }//end if
3655
                }//end foreach
3656
            }//end for
3657
        }//end for
3658

3659
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,908✔
3660
            StatusWriter::write('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3661
        }
3662

3663
    }//end processAdditional()
636✔
3664

3665

3666
    /**
3667
     * Takes a token produced from <code>token_get_all()</code> and produces a
3668
     * more uniform token.
3669
     *
3670
     * @param string|array $token The token to convert.
3671
     *
3672
     * @return array The new token.
3673
     */
3674
    public static function standardiseToken($token)
1✔
3675
    {
3676
        if (isset($token[1]) === false) {
1✔
3677
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
1✔
3678
                return self::$resolveTokenCache[$token[0]];
1✔
3679
            }
3680
        } else {
3681
            $cacheKey = null;
1✔
3682
            if ($token[0] === T_STRING) {
1✔
3683
                $cacheKey = strtolower($token[1]);
1✔
3684
            } else if ($token[0] !== T_CURLY_OPEN) {
1✔
3685
                $cacheKey = $token[0];
1✔
3686
            }
3687

3688
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
1✔
3689
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3690
                $newToken['content'] = $token[1];
×
3691
                return $newToken;
×
3692
            }
3693
        }
3694

3695
        if (isset($token[1]) === false) {
1✔
3696
            return self::resolveSimpleToken($token[0]);
1✔
3697
        }
3698

3699
        if ($token[0] === T_STRING) {
1✔
3700
            switch ($cacheKey) {
1✔
3701
            case 'false':
1✔
3702
                $newToken['type'] = 'T_FALSE';
1✔
3703
                break;
1✔
3704
            case 'true':
1✔
3705
                $newToken['type'] = 'T_TRUE';
1✔
3706
                break;
1✔
3707
            case 'null':
1✔
3708
                $newToken['type'] = 'T_NULL';
1✔
3709
                break;
1✔
3710
            case 'self':
1✔
3711
                $newToken['type'] = 'T_SELF';
1✔
3712
                break;
1✔
3713
            case 'parent':
1✔
3714
                $newToken['type'] = 'T_PARENT';
1✔
3715
                break;
1✔
3716
            default:
3717
                $newToken['type'] = 'T_STRING';
1✔
3718
                break;
1✔
3719
            }
3720

3721
            $newToken['code'] = constant($newToken['type']);
1✔
3722

3723
            self::$resolveTokenCache[$cacheKey] = $newToken;
1✔
3724
        } else if ($token[0] === T_CURLY_OPEN) {
1✔
3725
            $newToken = [
3726
                'code' => T_OPEN_CURLY_BRACKET,
×
3727
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3728
            ];
3729
        } else {
3730
            $newToken = [
3731
                'code' => $token[0],
1✔
3732
                'type' => Tokens::tokenName($token[0]),
1✔
3733
            ];
3734

3735
            self::$resolveTokenCache[$token[0]] = $newToken;
1✔
3736
        }//end if
3737

3738
        $newToken['content'] = $token[1];
1✔
3739
        return $newToken;
1✔
3740

3741
    }//end standardiseToken()
3742

3743

3744
    /**
3745
     * Converts simple tokens into a format that conforms to complex tokens
3746
     * produced by token_get_all().
3747
     *
3748
     * Simple tokens are tokens that are not in array form when produced from
3749
     * token_get_all().
3750
     *
3751
     * @param string $token The simple token to convert.
3752
     *
3753
     * @return array The new token in array format.
3754
     */
3755
    public static function resolveSimpleToken($token)
1✔
3756
    {
3757
        $newToken = [];
1✔
3758

3759
        switch ($token) {
1✔
3760
        case '{':
1✔
3761
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1✔
3762
            break;
1✔
3763
        case '}':
1✔
3764
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1✔
3765
            break;
1✔
3766
        case '[':
1✔
3767
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1✔
3768
            break;
1✔
3769
        case ']':
1✔
3770
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1✔
3771
            break;
1✔
3772
        case '(':
1✔
3773
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
1✔
3774
            break;
1✔
3775
        case ')':
1✔
3776
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
1✔
3777
            break;
1✔
3778
        case ':':
1✔
3779
            $newToken['type'] = 'T_COLON';
1✔
3780
            break;
1✔
3781
        case '.':
1✔
3782
            $newToken['type'] = 'T_STRING_CONCAT';
1✔
3783
            break;
1✔
3784
        case ';':
1✔
3785
            $newToken['type'] = 'T_SEMICOLON';
1✔
3786
            break;
1✔
3787
        case '=':
1✔
3788
            $newToken['type'] = 'T_EQUAL';
1✔
3789
            break;
1✔
3790
        case '*':
1✔
3791
            $newToken['type'] = 'T_MULTIPLY';
1✔
3792
            break;
1✔
3793
        case '/':
1✔
3794
            $newToken['type'] = 'T_DIVIDE';
1✔
3795
            break;
1✔
3796
        case '+':
1✔
3797
            $newToken['type'] = 'T_PLUS';
1✔
3798
            break;
1✔
3799
        case '-':
1✔
3800
            $newToken['type'] = 'T_MINUS';
1✔
3801
            break;
1✔
3802
        case '%':
1✔
3803
            $newToken['type'] = 'T_MODULUS';
1✔
3804
            break;
1✔
3805
        case '^':
1✔
3806
            $newToken['type'] = 'T_BITWISE_XOR';
1✔
3807
            break;
1✔
3808
        case '&':
1✔
3809
            $newToken['type'] = 'T_BITWISE_AND';
1✔
3810
            break;
1✔
3811
        case '|':
1✔
3812
            $newToken['type'] = 'T_BITWISE_OR';
1✔
3813
            break;
1✔
3814
        case '~':
1✔
3815
            $newToken['type'] = 'T_BITWISE_NOT';
1✔
3816
            break;
1✔
3817
        case '<':
1✔
3818
            $newToken['type'] = 'T_LESS_THAN';
1✔
3819
            break;
1✔
3820
        case '>':
1✔
3821
            $newToken['type'] = 'T_GREATER_THAN';
1✔
3822
            break;
1✔
3823
        case '!':
1✔
3824
            $newToken['type'] = 'T_BOOLEAN_NOT';
1✔
3825
            break;
1✔
3826
        case ',':
1✔
3827
            $newToken['type'] = 'T_COMMA';
1✔
3828
            break;
1✔
3829
        case '@':
1✔
3830
            $newToken['type'] = 'T_ASPERAND';
1✔
3831
            break;
1✔
3832
        case '$':
1✔
3833
            $newToken['type'] = 'T_DOLLAR';
1✔
3834
            break;
1✔
3835
        case '`':
1✔
3836
            $newToken['type'] = 'T_BACKTICK';
1✔
3837
            break;
1✔
3838
        default:
3839
            $newToken['type'] = 'T_NONE';
×
3840
            break;
×
3841
        }//end switch
3842

3843
        $newToken['code']    = constant($newToken['type']);
1✔
3844
        $newToken['content'] = $token;
1✔
3845

3846
        self::$resolveTokenCache[$token] = $newToken;
1✔
3847
        return $newToken;
1✔
3848

3849
    }//end resolveSimpleToken()
3850

3851

3852
    /**
3853
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3854
     * Handle parenthesis balancing while searching for closing token
3855
     *
3856
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3857
     * @param int             $start        The starting position.
3858
     * @param string|string[] $openerTokens The opening character.
3859
     * @param string          $closerChar   The closing character.
3860
     *
3861
     * @return int|null The position of the closing token, if found. NULL otherwise.
3862
     */
3863
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3864
    {
3865
        $numTokens    = count($tokens);
51✔
3866
        $stack        = [0];
51✔
3867
        $closer       = null;
51✔
3868
        $openerTokens = (array) $openerTokens;
51✔
3869

3870
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3871
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3872
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3873
            ) {
3874
                $stack[] = $x;
51✔
3875
            } else if ($tokens[$x] === $closerChar) {
51✔
3876
                array_pop($stack);
51✔
3877
                if (empty($stack) === true) {
51✔
3878
                    $closer = $x;
51✔
3879
                    break;
51✔
3880
                }
3881
            }
3882
        }
3883

3884
        return $closer;
51✔
3885

3886
    }//end findCloser()
3887

3888

3889
    /**
3890
     * PHP 8 attributes parser for PHP < 8
3891
     * Handles single-line and multiline attributes.
3892
     *
3893
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3894
     * @param int   $stackPtr The current position in token array.
3895
     *
3896
     * @return array|null The array of parsed attribute tokens
3897
     */
3898
    private function parsePhpAttribute(array &$tokens, $stackPtr)
19✔
3899
    {
3900

3901
        $token = $tokens[$stackPtr];
19✔
3902

3903
        $commentBody = substr($token[1], 2);
19✔
3904
        $subTokens   = @token_get_all('<?php '.$commentBody);
19✔
3905

3906
        foreach ($subTokens as $i => $subToken) {
19✔
3907
            if (is_array($subToken) === true
19✔
3908
                && $subToken[0] === T_COMMENT
19✔
3909
                && strpos($subToken[1], '#[') === 0
19✔
3910
            ) {
3911
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
19✔
3912
                if ($reparsed !== null) {
19✔
3913
                    array_splice($subTokens, $i, 1, $reparsed);
19✔
3914
                } else {
3915
                    $subToken[0] = T_ATTRIBUTE;
×
3916
                }
3917
            }
3918
        }
3919

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

3922
        // Go looking for the close bracket.
3923
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3924
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
19✔
3925
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
19✔
3926
                if (is_array($token) === true) {
19✔
3927
                    $commentBody .= $token[1];
19✔
3928
                } else {
3929
                    $commentBody .= $token;
19✔
3930
                }
3931
            }
3932

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

3936
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3937
            if ($bracketCloser !== null) {
19✔
3938
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
19✔
3939
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
19✔
3940
            }
3941
        }
3942

3943
        if ($bracketCloser === null) {
19✔
3944
            return null;
19✔
3945
        }
3946

3947
        return $subTokens;
19✔
3948

3949
    }//end parsePhpAttribute()
3950

3951

3952
    /**
3953
     * Creates a map for the attributes tokens that surround other tokens.
3954
     *
3955
     * @return void
3956
     */
3957
    private function createAttributesNestingMap()
3✔
3958
    {
3959
        $map = [];
3✔
3960
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
3961
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
3962
                && $i === $this->tokens[$i]['attribute_opener']
3✔
3963
            ) {
3964
                if (empty($map) === false) {
3✔
3965
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3966
                }
3967

3968
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
3969
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
3970
                        = $this->tokens[$i]['attribute_closer'];
3✔
3971
                }
3972
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
3973
                && $i === $this->tokens[$i]['attribute_closer']
3✔
3974
            ) {
3975
                array_pop($map);
3✔
3976
                if (empty($map) === false) {
3✔
3977
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3978
                }
3979
            } else {
3980
                if (empty($map) === false) {
3✔
3981
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3982
                }
3983
            }//end if
3984
        }//end for
3985

3986
    }//end createAttributesNestingMap()
1✔
3987

3988

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

© 2025 Coveralls, Inc