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

PHPCSStandards / PHP_CodeSniffer / 14481666175

16 Apr 2025 12:00AM UTC coverage: 77.949%. First build
14481666175

Pull #1016

github

web-flow
Merge a34994738 into a78b0dd14
Pull Request #1016: Tokenizer/PHP: `T_GOTO_LABEL` no longer contains colon

33 of 35 new or added lines in 2 files covered. (94.29%)

19456 of 24960 relevant lines covered (77.95%)

79.07 hits per line

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

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

10
namespace PHP_CodeSniffer\Tokenizers;
11

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

16
class PHP extends Tokenizer
17
{
18

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

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

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

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

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

507

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

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

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

535
        $insideInlineIf         = [];
3,009✔
536
        $insideUseGroup         = false;
3,009✔
537
        $insideConstDeclaration = false;
3,009✔
538

539
        $commentTokenizer = new Comment();
3,009✔
540

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

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

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

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

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

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

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

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

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

608
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,009✔
609
                StatusWriter::write('');
×
610
            }
611

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

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

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

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

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

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

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

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

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

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

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

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

692
                        break;
2,243✔
693
                    }
694

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

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

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

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

713
                        break;
627✔
714
                    }
715
                }
716

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

820
                    $stackPtr++;
×
821
                }
822

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

827
            /*
828
                Parse doc blocks into something that can be easily iterated over.
829
            */
830

831
            if ($tokenIsArray === true
3,009✔
832
                && ($token[0] === T_DOC_COMMENT
3,009✔
833
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
3,009✔
834
            ) {
835
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
93✔
836
                foreach ($commentTokens as $commentToken) {
93✔
837
                    $finalTokens[$newStackPtr] = $commentToken;
93✔
838
                    $newStackPtr++;
93✔
839
                }
840

841
                continue;
93✔
842
            }
843

844
            /*
845
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
846
            */
847

848
            if (PHP_VERSION_ID >= 80000
3,009✔
849
                && $tokenIsArray === true
3,009✔
850
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
3,009✔
851
                && isset($tokens[($stackPtr + 1)]) === true
3,009✔
852
                && is_array($tokens[($stackPtr + 1)]) === true
3,009✔
853
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,009✔
854
            ) {
855
                $nextToken = $tokens[($stackPtr + 1)];
1,548✔
856

857
                // If the next token is a single new line, merge it into the comment token
858
                // and set to it up to be skipped.
859
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
1,548✔
860
                    $token[1] .= $nextToken[1];
1,112✔
861
                    $tokens[($stackPtr + 1)] = null;
1,112✔
862

863
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,112✔
864
                        StatusWriter::write("* merged newline after comment into comment token $stackPtr", 2);
556✔
865
                    }
866
                } else {
867
                    // This may be a whitespace token consisting of multiple new lines.
868
                    if (strpos($nextToken[1], "\r\n") === 0) {
1,208✔
869
                        $token[1] .= "\r\n";
30✔
870
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
30✔
871
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
1,178✔
872
                        $token[1] .= "\n\r";
×
873
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
874
                    } else if (strpos($nextToken[1], "\n") === 0) {
1,178✔
875
                        $token[1] .= "\n";
1,178✔
876
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
1,178✔
877
                    }
878

879
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,208✔
880
                        StatusWriter::write("* stripped first newline after comment and added it to comment token $stackPtr", 2);
×
881
                    }
882
                }//end if
883
            }//end if
884

885
            /*
886
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
887
                T_LNUMBER and T_STRING token values into a single token value, and
888
                then ignore the T_STRING token.
889
            */
890

891
            if (PHP_VERSION_ID < 80100
3,009✔
892
                && $tokenIsArray === true && $token[1] === '0'
3,009✔
893
                && (isset($tokens[($stackPtr + 1)]) === true
2,565✔
894
                && is_array($tokens[($stackPtr + 1)]) === true
2,565✔
895
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,565✔
896
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,565✔
897
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,565✔
898
                && $tokens[($stackPtr + 1)][1][1] !== '_')
3,009✔
899
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
3,009✔
900
            ) {
901
                $finalTokens[$newStackPtr] = [
37✔
902
                    'code'    => T_LNUMBER,
37✔
903
                    'type'    => 'T_LNUMBER',
37✔
904
                    'content' => $token[1] .= $matches[1],
37✔
905
                ];
906
                $newStackPtr++;
37✔
907

908
                if (isset($matches[2]) === true && $matches[2] !== '') {
37✔
909
                    $type = 'T_LNUMBER';
10✔
910
                    if ($matches[2][0] === '_') {
10✔
911
                        $type = 'T_STRING';
10✔
912
                    }
913

914
                    $finalTokens[$newStackPtr] = [
10✔
915
                        'code'    => constant($type),
10✔
916
                        'type'    => $type,
10✔
917
                        'content' => $matches[2],
10✔
918
                    ];
919
                    $newStackPtr++;
10✔
920
                }
921

922
                $stackPtr++;
37✔
923
                continue;
37✔
924
            }//end if
925

926
            /*
927
                PHP 8.1 introduced two dedicated tokens for the & character.
928
                Retokenizing both of these to T_BITWISE_AND, which is the
929
                token PHPCS already tokenized them as.
930
            */
931

932
            if ($tokenIsArray === true
3,009✔
933
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
3,009✔
934
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
3,009✔
935
            ) {
936
                $finalTokens[$newStackPtr] = [
860✔
937
                    'code'    => T_BITWISE_AND,
860✔
938
                    'type'    => 'T_BITWISE_AND',
860✔
939
                    'content' => $token[1],
860✔
940
                ];
860✔
941
                $newStackPtr++;
860✔
942
                continue;
860✔
943
            }
944

945
            /*
946
                If this is a double quoted string, PHP will tokenize the whole
947
                thing which causes problems with the scope map when braces are
948
                within the string. So we need to merge the tokens together to
949
                provide a single string.
950
            */
951

952
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
3,009✔
953
                // Binary casts need a special token.
954
                if ($token[0] === 'b"') {
105✔
955
                    $finalTokens[$newStackPtr] = [
×
956
                        'code'    => T_BINARY_CAST,
×
957
                        'type'    => 'T_BINARY_CAST',
×
958
                        'content' => 'b',
×
959
                    ];
960
                    $newStackPtr++;
×
961
                }
962

963
                $tokenContent = '"';
105✔
964
                $nestedVars   = [];
105✔
965
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
105✔
966
                    $subToken        = (array) $tokens[$i];
105✔
967
                    $subTokenIsArray = isset($subToken[1]);
105✔
968

969
                    if ($subTokenIsArray === true) {
105✔
970
                        $tokenContent .= $subToken[1];
105✔
971
                        if (($subToken[1] === '{'
105✔
972
                            || $subToken[1] === '${')
105✔
973
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
105✔
974
                        ) {
975
                            $nestedVars[] = $i;
90✔
976
                        }
977
                    } else {
978
                        $tokenContent .= $subToken[0];
105✔
979
                        if ($subToken[0] === '}') {
105✔
980
                            array_pop($nestedVars);
60✔
981
                        }
982
                    }
983

984
                    if ($subTokenIsArray === false
105✔
985
                        && $subToken[0] === '"'
105✔
986
                        && empty($nestedVars) === true
105✔
987
                    ) {
988
                        // We found the other end of the double quoted string.
989
                        break;
105✔
990
                    }
991
                }//end for
992

993
                $stackPtr = $i;
105✔
994

995
                // Convert each line within the double quoted string to a
996
                // new token, so it conforms with other multiple line tokens.
997
                $tokenLines = explode($this->eolChar, $tokenContent);
105✔
998
                $numLines   = count($tokenLines);
105✔
999
                $newToken   = [];
105✔
1000

1001
                for ($j = 0; $j < $numLines; $j++) {
105✔
1002
                    $newToken['content'] = $tokenLines[$j];
105✔
1003
                    if ($j === ($numLines - 1)) {
105✔
1004
                        if ($tokenLines[$j] === '') {
105✔
1005
                            break;
90✔
1006
                        }
1007
                    } else {
1008
                        $newToken['content'] .= $this->eolChar;
60✔
1009
                    }
1010

1011
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
105✔
1012
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
105✔
1013
                    $finalTokens[$newStackPtr] = $newToken;
105✔
1014
                    $newStackPtr++;
105✔
1015
                }
1016

1017
                // Continue, as we're done with this token.
1018
                continue;
105✔
1019
            }//end if
1020

1021
            /*
1022
                Detect binary casting and assign the casts their own token.
1023
            */
1024

1025
            if ($tokenIsArray === true
3,009✔
1026
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
3,009✔
1027
                && (substr($token[1], 0, 2) === 'b"'
2,901✔
1028
                || substr($token[1], 0, 2) === "b'")
3,009✔
1029
            ) {
1030
                $finalTokens[$newStackPtr] = [
×
1031
                    'code'    => T_BINARY_CAST,
×
1032
                    'type'    => 'T_BINARY_CAST',
×
1033
                    'content' => 'b',
×
1034
                ];
1035
                $newStackPtr++;
×
1036
                $token[1] = substr($token[1], 1);
×
1037
            }
1038

1039
            if ($tokenIsArray === true
3,009✔
1040
                && $token[0] === T_STRING_CAST
3,009✔
1041
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
3,009✔
1042
            ) {
1043
                $finalTokens[$newStackPtr] = [
×
1044
                    'code'    => T_BINARY_CAST,
×
1045
                    'type'    => 'T_BINARY_CAST',
×
1046
                    'content' => $token[1],
×
1047
                ];
1048
                $newStackPtr++;
×
1049
                continue;
×
1050
            }
1051

1052
            /*
1053
                If this is a heredoc, PHP will tokenize the whole
1054
                thing which causes problems when heredocs don't
1055
                contain real PHP code, which is almost never.
1056
                We want to leave the start and end heredoc tokens
1057
                alone though.
1058
            */
1059

1060
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
3,009✔
1061
                // Add the start heredoc token to the final array.
1062
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
135✔
1063

1064
                // Check if this is actually a nowdoc and use a different token
1065
                // to help the sniffs.
1066
                $nowdoc = false;
135✔
1067
                if (strpos($token[1], "'") !== false) {
135✔
1068
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
18✔
1069
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
18✔
1070
                    $nowdoc = true;
18✔
1071
                }
1072

1073
                $tokenContent = '';
135✔
1074
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
135✔
1075
                    $subTokenIsArray = is_array($tokens[$i]);
135✔
1076
                    if ($subTokenIsArray === true
135✔
1077
                        && $tokens[$i][0] === T_END_HEREDOC
135✔
1078
                    ) {
1079
                        // We found the other end of the heredoc.
1080
                        break;
132✔
1081
                    }
1082

1083
                    if ($subTokenIsArray === true) {
135✔
1084
                        $tokenContent .= $tokens[$i][1];
135✔
1085
                    } else {
1086
                        $tokenContent .= $tokens[$i];
114✔
1087
                    }
1088
                }
1089

1090
                if ($i === $numTokens) {
135✔
1091
                    // We got to the end of the file and never
1092
                    // found the closing token, so this probably wasn't
1093
                    // a heredoc.
1094
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
1095
                        $type = $finalTokens[$newStackPtr]['type'];
×
1096
                        StatusWriter::write('* failed to find the end of the here/nowdoc', 2);
×
1097
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1098
                    }
1099

1100
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
3✔
1101
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
3✔
1102
                    $newStackPtr++;
3✔
1103
                    continue;
3✔
1104
                }
1105

1106
                $stackPtr = $i;
132✔
1107
                $newStackPtr++;
132✔
1108

1109
                // Convert each line within the heredoc to a
1110
                // new token, so it conforms with other multiple line tokens.
1111
                $tokenLines = explode($this->eolChar, $tokenContent);
132✔
1112
                $numLines   = count($tokenLines);
132✔
1113
                $newToken   = [];
132✔
1114

1115
                for ($j = 0; $j < $numLines; $j++) {
132✔
1116
                    $newToken['content'] = $tokenLines[$j];
132✔
1117
                    if ($j === ($numLines - 1)) {
132✔
1118
                        if ($tokenLines[$j] === '') {
132✔
1119
                            break;
132✔
1120
                        }
1121
                    } else {
1122
                        $newToken['content'] .= $this->eolChar;
132✔
1123
                    }
1124

1125
                    if ($nowdoc === true) {
132✔
1126
                        $newToken['code'] = T_NOWDOC;
18✔
1127
                        $newToken['type'] = 'T_NOWDOC';
18✔
1128
                    } else {
1129
                        $newToken['code'] = T_HEREDOC;
132✔
1130
                        $newToken['type'] = 'T_HEREDOC';
132✔
1131
                    }
1132

1133
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1134
                    $newStackPtr++;
132✔
1135
                }//end for
1136

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

1140
                if ($nowdoc === true) {
132✔
1141
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
18✔
1142
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
18✔
1143
                }
1144

1145
                $newStackPtr++;
132✔
1146

1147
                // Continue, as we're done with this token.
1148
                continue;
132✔
1149
            }//end if
1150

1151
            /*
1152
                Enum keyword for PHP < 8.1
1153
            */
1154

1155
            if ($tokenIsArray === true
3,009✔
1156
                && $token[0] === T_STRING
3,009✔
1157
                && strtolower($token[1]) === 'enum'
3,009✔
1158
            ) {
1159
                // Get the next non-empty token.
1160
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,356✔
1161
                    if (is_array($tokens[$i]) === false
1,356✔
1162
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,356✔
1163
                    ) {
1164
                        break;
1,356✔
1165
                    }
1166
                }
1167

1168
                if (isset($tokens[$i]) === true
1,356✔
1169
                    && is_array($tokens[$i]) === true
1,356✔
1170
                    && $tokens[$i][0] === T_STRING
1,356✔
1171
                ) {
1172
                    // Modify $tokens directly so we can use it later when converting enum "case".
1173
                    $tokens[$stackPtr][0] = T_ENUM;
313✔
1174

1175
                    $newToken            = [];
313✔
1176
                    $newToken['code']    = T_ENUM;
313✔
1177
                    $newToken['type']    = 'T_ENUM';
313✔
1178
                    $newToken['content'] = $token[1];
313✔
1179
                    $finalTokens[$newStackPtr] = $newToken;
313✔
1180

1181
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
313✔
1182
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_ENUM", 2);
×
1183
                    }
1184

1185
                    $newStackPtr++;
313✔
1186
                    continue;
313✔
1187
                }
1188
            }//end if
1189

1190
            /*
1191
                Convert enum "case" to T_ENUM_CASE
1192
            */
1193

1194
            if ($tokenIsArray === true
3,009✔
1195
                && $token[0] === T_CASE
3,009✔
1196
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,009✔
1197
            ) {
1198
                $isEnumCase = false;
1,554✔
1199
                $scope      = 1;
1,554✔
1200

1201
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,554✔
1202
                    if ($tokens[$i] === '}') {
1,554✔
1203
                        $scope++;
1,509✔
1204
                        continue;
1,509✔
1205
                    }
1206

1207
                    if ($tokens[$i] === '{') {
1,554✔
1208
                        $scope--;
1,554✔
1209
                        continue;
1,554✔
1210
                    }
1211

1212
                    if (is_array($tokens[$i]) === false) {
1,554✔
1213
                        continue;
1,554✔
1214
                    }
1215

1216
                    if ($scope !== 0) {
1,554✔
1217
                        continue;
1,554✔
1218
                    }
1219

1220
                    if ($tokens[$i][0] === T_SWITCH) {
1,044✔
1221
                        break;
978✔
1222
                    }
1223

1224
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,044✔
1225
                        $isEnumCase = true;
129✔
1226
                        break;
129✔
1227
                    }
1228
                }//end for
1229

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

1234
                    $newToken            = [];
129✔
1235
                    $newToken['code']    = T_ENUM_CASE;
129✔
1236
                    $newToken['type']    = 'T_ENUM_CASE';
129✔
1237
                    $newToken['content'] = $token[1];
129✔
1238
                    $finalTokens[$newStackPtr] = $newToken;
129✔
1239

1240
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
129✔
1241
                        StatusWriter::write("* token $stackPtr changed from T_CASE to T_ENUM_CASE", 2);
×
1242
                    }
1243

1244
                    $newStackPtr++;
129✔
1245
                    continue;
129✔
1246
                }
1247
            }//end if
1248

1249
            /*
1250
                As of PHP 8.0 fully qualified, partially qualified and namespace relative
1251
                identifier names are tokenized differently.
1252
                This "undoes" the new tokenization so the tokenization will be the same in
1253
                in PHP 5, 7 and 8.
1254
            */
1255

1256
            if (PHP_VERSION_ID >= 80000
3,009✔
1257
                && $tokenIsArray === true
3,009✔
1258
                && ($token[0] === T_NAME_QUALIFIED
2,006✔
1259
                || $token[0] === T_NAME_FULLY_QUALIFIED
2,006✔
1260
                || $token[0] === T_NAME_RELATIVE)
3,009✔
1261
            ) {
1262
                $name = $token[1];
1,244✔
1263

1264
                if ($token[0] === T_NAME_FULLY_QUALIFIED) {
1,244✔
1265
                    $newToken            = [];
708✔
1266
                    $newToken['code']    = T_NS_SEPARATOR;
708✔
1267
                    $newToken['type']    = 'T_NS_SEPARATOR';
708✔
1268
                    $newToken['content'] = '\\';
708✔
1269
                    $finalTokens[$newStackPtr] = $newToken;
708✔
1270
                    ++$newStackPtr;
708✔
1271

1272
                    $name = ltrim($name, '\\');
708✔
1273
                }
1274

1275
                if ($token[0] === T_NAME_RELATIVE) {
1,244✔
1276
                    $newToken            = [];
1,180✔
1277
                    $newToken['code']    = T_NAMESPACE;
1,180✔
1278
                    $newToken['type']    = 'T_NAMESPACE';
1,180✔
1279
                    $newToken['content'] = substr($name, 0, 9);
1,180✔
1280
                    $finalTokens[$newStackPtr] = $newToken;
1,180✔
1281
                    ++$newStackPtr;
1,180✔
1282

1283
                    $newToken            = [];
1,180✔
1284
                    $newToken['code']    = T_NS_SEPARATOR;
1,180✔
1285
                    $newToken['type']    = 'T_NS_SEPARATOR';
1,180✔
1286
                    $newToken['content'] = '\\';
1,180✔
1287
                    $finalTokens[$newStackPtr] = $newToken;
1,180✔
1288
                    ++$newStackPtr;
1,180✔
1289

1290
                    $name = substr($name, 10);
1,180✔
1291
                }
1292

1293
                $parts     = explode('\\', $name);
1,244✔
1294
                $partCount = count($parts);
1,244✔
1295
                $lastPart  = ($partCount - 1);
1,244✔
1296

1297
                foreach ($parts as $i => $part) {
1,244✔
1298
                    $newToken            = [];
1,244✔
1299
                    $newToken['code']    = T_STRING;
1,244✔
1300
                    $newToken['type']    = 'T_STRING';
1,244✔
1301
                    $newToken['content'] = $part;
1,244✔
1302
                    $finalTokens[$newStackPtr] = $newToken;
1,244✔
1303
                    ++$newStackPtr;
1,244✔
1304

1305
                    if ($i !== $lastPart) {
1,244✔
1306
                        $newToken            = [];
904✔
1307
                        $newToken['code']    = T_NS_SEPARATOR;
904✔
1308
                        $newToken['type']    = 'T_NS_SEPARATOR';
904✔
1309
                        $newToken['content'] = '\\';
904✔
1310
                        $finalTokens[$newStackPtr] = $newToken;
904✔
1311
                        ++$newStackPtr;
904✔
1312
                    }
1313
                }
1314

1315
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,244✔
1316
                    $type    = Tokens::tokenName($token[0]);
×
1317
                    $content = Common::prepareForOutput($token[1]);
×
1318
                    StatusWriter::write("* token $stackPtr split into individual tokens; was: $type => $content", 2);
×
1319
                }
1320

1321
                continue;
1,244✔
1322
            }//end if
1323

1324
            /*
1325
                PHP 8.0 Attributes
1326
            */
1327

1328
            if (PHP_VERSION_ID < 80000
3,009✔
1329
                && $token[0] === T_COMMENT
3,009✔
1330
                && strpos($token[1], '#[') === 0
3,009✔
1331
            ) {
1332
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
17✔
1333
                if ($subTokens !== null) {
17✔
1334
                    array_splice($tokens, $stackPtr, 1, $subTokens);
17✔
1335
                    $numTokens = count($tokens);
17✔
1336

1337
                    $tokenIsArray = true;
17✔
1338
                    $token        = $tokens[$stackPtr];
17✔
1339
                } else {
1340
                    $token[0] = T_ATTRIBUTE;
17✔
1341
                }
1342
            }
1343

1344
            if ($tokenIsArray === true
3,009✔
1345
                && $token[0] === T_ATTRIBUTE
3,009✔
1346
            ) {
1347
                // Go looking for the close bracket.
1348
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1349

1350
                $newToken            = [];
51✔
1351
                $newToken['code']    = T_ATTRIBUTE;
51✔
1352
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1353
                $newToken['content'] = '#[';
51✔
1354
                $finalTokens[$newStackPtr] = $newToken;
51✔
1355

1356
                $tokens[$bracketCloser]    = [];
51✔
1357
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1358
                $tokens[$bracketCloser][1] = ']';
51✔
1359

1360
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1361
                    StatusWriter::write("* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END", 2);
×
1362
                }
1363

1364
                $newStackPtr++;
51✔
1365
                continue;
51✔
1366
            }//end if
1367

1368
            /*
1369
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1370
                token and ensures that the colon after it is always T_COLON.
1371
            */
1372

1373
            if ($tokenIsArray === true
3,009✔
1374
                && ($token[0] === T_STRING
3,009✔
1375
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
3,009✔
1376
            ) {
1377
                // Get the next non-empty token.
1378
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,823✔
1379
                    if (is_array($tokens[$i]) === false
2,823✔
1380
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
2,823✔
1381
                    ) {
1382
                        break;
2,823✔
1383
                    }
1384
                }
1385

1386
                if (isset($tokens[$i]) === true
2,823✔
1387
                    && is_array($tokens[$i]) === false
2,823✔
1388
                    && $tokens[$i] === ':'
2,823✔
1389
                ) {
1390
                    // Get the previous non-empty token.
1391
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,626✔
1392
                        if (is_array($tokens[$j]) === false
1,626✔
1393
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
1,626✔
1394
                        ) {
1395
                            break;
1,626✔
1396
                        }
1397
                    }
1398

1399
                    if (is_array($tokens[$j]) === false
1,626✔
1400
                        && ($tokens[$j] === '('
1,576✔
1401
                        || $tokens[$j] === ',')
1,626✔
1402
                    ) {
1403
                        $newToken            = [];
690✔
1404
                        $newToken['code']    = T_PARAM_NAME;
690✔
1405
                        $newToken['type']    = 'T_PARAM_NAME';
690✔
1406
                        $newToken['content'] = $token[1];
690✔
1407
                        $finalTokens[$newStackPtr] = $newToken;
690✔
1408

1409
                        $newStackPtr++;
690✔
1410

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

1415
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
690✔
1416
                            $type = Tokens::tokenName($token[0]);
×
1417
                            StatusWriter::write("* token $stackPtr changed from $type to T_PARAM_NAME", 2);
×
1418
                        }
1419

1420
                        continue;
690✔
1421
                    }
1422
                }//end if
1423
            }//end if
1424

1425
            /*
1426
                "readonly" keyword for PHP < 8.1
1427
            */
1428

1429
            if ($tokenIsArray === true
3,009✔
1430
                && strtolower($token[1]) === 'readonly'
3,009✔
1431
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,338✔
1432
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
3,009✔
1433
            ) {
1434
                // Get the next non-whitespace token.
1435
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
996✔
1436
                    if (is_array($tokens[$i]) === false
996✔
1437
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
996✔
1438
                    ) {
1439
                        break;
996✔
1440
                    }
1441
                }
1442

1443
                $isReadonlyKeyword = false;
996✔
1444

1445
                if (isset($tokens[$i]) === false
996✔
1446
                    || $tokens[$i] !== '('
996✔
1447
                ) {
1448
                    $isReadonlyKeyword = true;
996✔
1449
                } else if ($tokens[$i] === '(') {
×
1450
                    /*
1451
                     * Skip over tokens which can be used in type declarations.
1452
                     * At this point, the only token types which need to be taken into consideration
1453
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1454
                     * and the union/intersection/dnf parentheses.
1455
                     */
1456

1457
                    $foundDNFParens = 1;
×
1458
                    $foundDNFPipe   = 0;
×
1459

1460
                    for (++$i; $i < $numTokens; $i++) {
×
1461
                        if (is_array($tokens[$i]) === true) {
×
1462
                            $tokenType = $tokens[$i][0];
×
1463
                        } else {
1464
                            $tokenType = $tokens[$i];
×
1465
                        }
1466

1467
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1468
                            continue;
×
1469
                        }
1470

1471
                        if ($tokenType === '|') {
×
1472
                            ++$foundDNFPipe;
×
1473
                            continue;
×
1474
                        }
1475

1476
                        if ($tokenType === ')') {
×
1477
                            ++$foundDNFParens;
×
1478
                            continue;
×
1479
                        }
1480

1481
                        if ($tokenType === '(') {
×
1482
                            ++$foundDNFParens;
×
1483
                            continue;
×
1484
                        }
1485

1486
                        if ($tokenType === T_STRING
×
1487
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1488
                            || $tokenType === T_NAME_RELATIVE
×
1489
                            || $tokenType === T_NAME_QUALIFIED
×
1490
                            || $tokenType === T_ARRAY
×
1491
                            || $tokenType === T_NAMESPACE
×
1492
                            || $tokenType === T_NS_SEPARATOR
×
1493
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1494
                            || $tokenType === '&' // PHP < 8.0.
×
1495
                        ) {
1496
                            continue;
×
1497
                        }
1498

1499
                        // Reached the next token after.
1500
                        if (($foundDNFParens % 2) === 0
×
1501
                            && $foundDNFPipe >= 1
×
1502
                            && ($tokenType === T_VARIABLE
×
1503
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1504
                        ) {
1505
                            $isReadonlyKeyword = true;
×
1506
                        }
1507

1508
                        break;
×
1509
                    }//end for
1510
                }//end if
1511

1512
                if ($isReadonlyKeyword === true) {
996✔
1513
                    $finalTokens[$newStackPtr] = [
996✔
1514
                        'code'    => T_READONLY,
996✔
1515
                        'type'    => 'T_READONLY',
996✔
1516
                        'content' => $token[1],
996✔
1517
                    ];
664✔
1518
                    $newStackPtr++;
996✔
1519

1520
                    // Also modify the original token stack so that
1521
                    // future checks (like looking for T_NULLABLE) can
1522
                    // detect the T_READONLY token more easily.
1523
                    $tokens[$stackPtr][0] = T_READONLY;
996✔
1524
                    $token[0] = T_READONLY;
996✔
1525

1526
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
996✔
1527
                        StatusWriter::write("* token $stackPtr changed from $type to T_READONLY", 2);
664✔
1528
                    }
1529
                } else {
1530
                    $finalTokens[$newStackPtr] = [
×
1531
                        'code'    => T_STRING,
×
1532
                        'type'    => 'T_STRING',
×
1533
                        'content' => $token[1],
×
1534
                    ];
1535
                    $newStackPtr++;
×
1536

1537
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1538
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1539
                    }
1540
                }//end if
1541

1542
                continue;
996✔
1543
            }//end if
1544

1545
            /*
1546
                Deal with "yield from" in various PHP versions.
1547
            */
1548

1549
            if (PHP_VERSION_ID < 80300
3,009✔
1550
                && $tokenIsArray === true
3,009✔
1551
                && $token[0] === T_STRING
3,009✔
1552
                && strtolower($token[1]) === 'from'
3,009✔
1553
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
3,009✔
1554
            ) {
1555
                /*
1556
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1557
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1558
                    We want to keep the tokenization of the tokens between, but need to change the
1559
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1560
                */
1561

1562
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
36✔
1563
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
36✔
1564

1565
                $finalTokens[$newStackPtr] = [
36✔
1566
                    'code'    => T_YIELD_FROM,
36✔
1567
                    'type'    => 'T_YIELD_FROM',
36✔
1568
                    'content' => $token[1],
36✔
1569
                ];
18✔
1570
                $newStackPtr++;
36✔
1571

1572
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
36✔
1573
                    StatusWriter::write("* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD", 2);
×
1574
                    StatusWriter::write("* token $stackPtr changed into T_YIELD_FROM; was: T_STRING", 2);
×
1575
                }
1576

1577
                continue;
36✔
1578
            } else if ($tokenIsArray === true
3,009✔
1579
                && $token[0] === T_YIELD_FROM
3,009✔
1580
                && strpos($token[1], $this->eolChar) !== false
3,009✔
1581
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
3,009✔
1582
            ) {
1583
                /*
1584
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1585
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1586
                    separately for consistency.
1587
                */
1588

1589
                $finalTokens[$newStackPtr] = [
54✔
1590
                    'code'    => T_YIELD_FROM,
54✔
1591
                    'type'    => 'T_YIELD_FROM',
54✔
1592
                    'content' => substr($token[1], 0, 5),
54✔
1593
                ];
36✔
1594
                $newStackPtr++;
54✔
1595

1596
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
54✔
1597
                $numLines   = count($tokenLines);
54✔
1598
                $newToken   = [
36✔
1599
                    'type'    => 'T_WHITESPACE',
54✔
1600
                    'code'    => T_WHITESPACE,
54✔
1601
                    'content' => '',
54✔
1602
                ];
36✔
1603

1604
                foreach ($tokenLines as $i => $line) {
54✔
1605
                    $newToken['content'] = $line;
54✔
1606
                    if ($i === ($numLines - 1)) {
54✔
1607
                        if ($line === '') {
54✔
1608
                            break;
36✔
1609
                        }
1610
                    } else {
1611
                        $newToken['content'] .= $this->eolChar;
54✔
1612
                    }
1613

1614
                    $finalTokens[$newStackPtr] = $newToken;
54✔
1615
                    $newStackPtr++;
54✔
1616
                }
1617

1618
                $finalTokens[$newStackPtr] = [
54✔
1619
                    'code'    => T_YIELD_FROM,
54✔
1620
                    'type'    => 'T_YIELD_FROM',
54✔
1621
                    'content' => substr($token[1], -4),
54✔
1622
                ];
36✔
1623
                $newStackPtr++;
54✔
1624

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

1629
                continue;
54✔
1630
            } else if (PHP_VERSION_ID >= 80300
3,009✔
1631
                && $tokenIsArray === true
3,009✔
1632
                && $token[0] === T_YIELD_FROM
3,009✔
1633
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
3,009✔
1634
                && stripos($token[1], 'yield') === 0
3,009✔
1635
            ) {
1636
                /*
1637
                    Since PHP 8.3, "yield from" allows for comments and will
1638
                    swallow the comment in the `T_YIELD_FROM` token.
1639
                    We need to split this up to allow for sniffs handling comments.
1640
                */
1641

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

1649
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
18✔
1650
                // Remove the PHP open tag token.
1651
                array_shift($yieldFromSubtokens);
18✔
1652
                // Add the "from" keyword.
1653
                $yieldFromSubtokens[] = [
18✔
1654
                    0 => T_YIELD_FROM,
18✔
1655
                    1 => substr($token[1], -4),
18✔
1656
                ];
18✔
1657

1658
                // Inject the new tokens into the token stack.
1659
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
18✔
1660
                $numTokens = count($tokens);
18✔
1661

1662
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
1663
                    StatusWriter::write("* token $stackPtr split into parts (yield from with comment)", 2);
×
1664
                }
1665

1666
                unset($yieldFromSubtokens);
18✔
1667
                continue;
18✔
1668
            }//end if
1669

1670
            /*
1671
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1672
                T_COALESCE, T_EQUAL.
1673
                So look for and combine these tokens in earlier versions.
1674
            */
1675

1676
            if ($tokenIsArray === true
3,009✔
1677
                && $token[0] === T_COALESCE
3,009✔
1678
                && isset($tokens[($stackPtr + 1)]) === true
3,009✔
1679
                && $tokens[($stackPtr + 1)][0] === '='
3,009✔
1680
            ) {
1681
                $newToken            = [];
×
1682
                $newToken['code']    = T_COALESCE_EQUAL;
×
1683
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1684
                $newToken['content'] = '??=';
×
1685
                $finalTokens[$newStackPtr] = $newToken;
×
1686

1687
                $newStackPtr++;
×
1688
                $stackPtr++;
×
1689

1690
                continue;
×
1691
            }
1692

1693
            /*
1694
                Before PHP 8, the ?-> operator was tokenized as
1695
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1696
                So look for and combine these tokens in earlier versions.
1697
            */
1698

1699
            if ($tokenIsArray === false
3,009✔
1700
                && $token[0] === '?'
3,009✔
1701
                && isset($tokens[($stackPtr + 1)]) === true
3,009✔
1702
                && is_array($tokens[($stackPtr + 1)]) === true
3,009✔
1703
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
3,009✔
1704
            ) {
1705
                $newToken            = [];
25✔
1706
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
25✔
1707
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
25✔
1708
                $newToken['content'] = '?->';
25✔
1709
                $finalTokens[$newStackPtr] = $newToken;
25✔
1710

1711
                $newStackPtr++;
25✔
1712
                $stackPtr++;
25✔
1713
                continue;
25✔
1714
            }
1715

1716
            /*
1717
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1718
                tokens split the token with a T_STRING. So look for
1719
                and change these tokens in earlier versions.
1720
            */
1721

1722
            if (PHP_VERSION_ID < 70400
3,009✔
1723
                && ($tokenIsArray === true
3,009✔
1724
                && ($token[0] === T_LNUMBER
3,009✔
1725
                || $token[0] === T_DNUMBER)
3,009✔
1726
                && isset($tokens[($stackPtr + 1)]) === true
3,009✔
1727
                && is_array($tokens[($stackPtr + 1)]) === true
3,009✔
1728
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,009✔
1729
                && $tokens[($stackPtr + 1)][1][0] === '_')
3,009✔
1730
            ) {
1731
                $newContent = $token[1];
27✔
1732
                $newType    = $token[0];
27✔
1733
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
27✔
1734
                    if (is_array($tokens[$i]) === false) {
27✔
1735
                        break;
27✔
1736
                    }
1737

1738
                    if ($tokens[$i][0] === T_LNUMBER
27✔
1739
                        || $tokens[$i][0] === T_DNUMBER
27✔
1740
                    ) {
1741
                        $newContent .= $tokens[$i][1];
27✔
1742
                        continue;
27✔
1743
                    }
1744

1745
                    if ($tokens[$i][0] === T_STRING
27✔
1746
                        && $tokens[$i][1][0] === '_'
27✔
1747
                        && ((strpos($newContent, '0x') === 0
27✔
1748
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
27✔
1749
                        || (strpos($newContent, '0x') !== 0
27✔
1750
                        && substr($newContent, -1) !== '.'
27✔
1751
                        && substr(strtolower($newContent), -1) !== 'e'
27✔
1752
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
27✔
1753
                    ) {
1754
                        $newContent .= $tokens[$i][1];
27✔
1755

1756
                        // Support floats.
1757
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
27✔
1758
                            && ($tokens[($i + 1)] === '-'
27✔
1759
                            || $tokens[($i + 1)] === '+')
27✔
1760
                        ) {
1761
                            $newContent .= $tokens[($i + 1)];
27✔
1762
                            $i++;
27✔
1763
                        }
1764

1765
                        continue;
27✔
1766
                    }//end if
1767

1768
                    break;
27✔
1769
                }//end for
1770

1771
                if ($newType === T_LNUMBER
27✔
1772
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1773
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1774
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1775
                    || (stripos($newContent, '0x') !== 0
27✔
1776
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
27✔
1777
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
27✔
1778
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1779
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
27✔
1780
                ) {
1781
                    $newType = T_DNUMBER;
27✔
1782
                }
1783

1784
                $newToken            = [];
27✔
1785
                $newToken['code']    = $newType;
27✔
1786
                $newToken['type']    = Tokens::tokenName($newType);
27✔
1787
                $newToken['content'] = $newContent;
27✔
1788
                $finalTokens[$newStackPtr] = $newToken;
27✔
1789

1790
                $newStackPtr++;
27✔
1791
                $stackPtr = ($i - 1);
27✔
1792
                continue;
27✔
1793
            }//end if
1794

1795
            /*
1796
                Backfill the T_MATCH token for PHP versions < 8.0 and
1797
                do initial correction for non-match expression T_MATCH tokens
1798
                to T_STRING for PHP >= 8.0.
1799
                A final check for non-match expression T_MATCH tokens is done
1800
                in PHP::processAdditional().
1801
            */
1802

1803
            if ($tokenIsArray === true
3,009✔
1804
                && (($token[0] === T_STRING
3,009✔
1805
                && strtolower($token[1]) === 'match')
2,896✔
1806
                || $token[0] === T_MATCH)
3,009✔
1807
            ) {
1808
                $isMatch = false;
741✔
1809
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
741✔
1810
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
741✔
1811
                        continue;
741✔
1812
                    }
1813

1814
                    if ($tokens[$x] !== '(') {
741✔
1815
                        // This is not a match expression.
1816
                        break;
353✔
1817
                    }
1818

1819
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
741✔
1820
                        // Also not a match expression.
1821
                        break;
183✔
1822
                    }
1823

1824
                    $isMatch = true;
741✔
1825
                    break;
741✔
1826
                }//end for
1827

1828
                if ($isMatch === true && $token[0] === T_STRING) {
741✔
1829
                    $newToken            = [];
247✔
1830
                    $newToken['code']    = T_MATCH;
247✔
1831
                    $newToken['type']    = 'T_MATCH';
247✔
1832
                    $newToken['content'] = $token[1];
247✔
1833

1834
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
247✔
1835
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_MATCH", 2);
×
1836
                    }
1837

1838
                    $finalTokens[$newStackPtr] = $newToken;
247✔
1839
                    $newStackPtr++;
247✔
1840
                    continue;
247✔
1841
                } else if ($isMatch === false && $token[0] === T_MATCH) {
725✔
1842
                    // PHP 8.0, match keyword, but not a match expression.
1843
                    $newToken            = [];
122✔
1844
                    $newToken['code']    = T_STRING;
122✔
1845
                    $newToken['type']    = 'T_STRING';
122✔
1846
                    $newToken['content'] = $token[1];
122✔
1847

1848
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
122✔
1849
                        StatusWriter::write("* token $stackPtr changed from T_MATCH to T_STRING", 2);
×
1850
                    }
1851

1852
                    $finalTokens[$newStackPtr] = $newToken;
122✔
1853
                    $newStackPtr++;
122✔
1854
                    continue;
122✔
1855
                }//end if
1856
            }//end if
1857

1858
            /*
1859
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
1860
                to prevent scope being set and the scope for switch default statements
1861
                breaking.
1862
            */
1863

1864
            if ($tokenIsArray === true
3,009✔
1865
                && $token[0] === T_DEFAULT
3,009✔
1866
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,009✔
1867
            ) {
1868
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,425✔
1869
                    if ($tokens[$x] === ',') {
1,425✔
1870
                        // Skip over potential trailing comma (supported in PHP).
1871
                        continue;
231✔
1872
                    }
1873

1874
                    if (is_array($tokens[$x]) === false
1,425✔
1875
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
1,425✔
1876
                    ) {
1877
                        // Non-empty, non-comma content.
1878
                        break;
1,425✔
1879
                    }
1880
                }
1881

1882
                if (isset($tokens[$x]) === true
1,425✔
1883
                    && is_array($tokens[$x]) === true
1,425✔
1884
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,425✔
1885
                ) {
1886
                    // Modify the original token stack for the double arrow so that
1887
                    // future checks can disregard the double arrow token more easily.
1888
                    // For match expression "case" statements, this is handled
1889
                    // in PHP::processAdditional().
1890
                    $tokens[$x][0] = T_MATCH_ARROW;
741✔
1891
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
741✔
1892
                        StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
1893
                    }
1894

1895
                    $newToken            = [];
741✔
1896
                    $newToken['code']    = T_MATCH_DEFAULT;
741✔
1897
                    $newToken['type']    = 'T_MATCH_DEFAULT';
741✔
1898
                    $newToken['content'] = $token[1];
741✔
1899

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

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

1910
            /*
1911
                Convert ? to T_NULLABLE OR T_INLINE_THEN
1912
            */
1913

1914
            if ($tokenIsArray === false && $token[0] === '?') {
3,009✔
1915
                $newToken            = [];
1,218✔
1916
                $newToken['content'] = '?';
1,218✔
1917

1918
                // For typed constants, we only need to check the token before the ? to be sure.
1919
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,218✔
1920
                    $newToken['code'] = T_NULLABLE;
189✔
1921
                    $newToken['type'] = 'T_NULLABLE';
189✔
1922

1923
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
189✔
1924
                        StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
1925
                    }
1926

1927
                    $finalTokens[$newStackPtr] = $newToken;
189✔
1928
                    $newStackPtr++;
189✔
1929
                    continue;
189✔
1930
                }
1931

1932
                /*
1933
                 * Check if the next non-empty token is one of the tokens which can be used
1934
                 * in type declarations. If not, it's definitely a ternary.
1935
                 * At this point, the only token types which need to be taken into consideration
1936
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
1937
                 */
1938

1939
                $lastRelevantNonEmpty = null;
1,218✔
1940

1941
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,218✔
1942
                    if (is_array($tokens[$i]) === true) {
1,218✔
1943
                        $tokenType = $tokens[$i][0];
1,218✔
1944
                    } else {
1945
                        $tokenType = $tokens[$i];
886✔
1946
                    }
1947

1948
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
1,218✔
1949
                        continue;
1,218✔
1950
                    }
1951

1952
                    if ($tokenType === T_STRING
1,218✔
1953
                        || $tokenType === T_NAME_FULLY_QUALIFIED
1,218✔
1954
                        || $tokenType === T_NAME_RELATIVE
1,218✔
1955
                        || $tokenType === T_NAME_QUALIFIED
1,218✔
1956
                        || $tokenType === T_ARRAY
1,218✔
1957
                        || $tokenType === T_NAMESPACE
1,218✔
1958
                        || $tokenType === T_NS_SEPARATOR
1,218✔
1959
                    ) {
1960
                        $lastRelevantNonEmpty = $tokenType;
886✔
1961
                        continue;
886✔
1962
                    }
1963

1964
                    if (($tokenType !== T_CALLABLE
1,218✔
1965
                        && isset($lastRelevantNonEmpty) === false)
1,218✔
1966
                        || ($lastRelevantNonEmpty === T_ARRAY
886✔
1967
                        && $tokenType === '(')
886✔
1968
                        || (($lastRelevantNonEmpty === T_STRING
1,148✔
1969
                        || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED
812✔
1970
                        || $lastRelevantNonEmpty === T_NAME_RELATIVE
812✔
1971
                        || $lastRelevantNonEmpty === T_NAME_QUALIFIED)
1,148✔
1972
                        && ($tokenType === T_DOUBLE_COLON
1,148✔
1973
                        || $tokenType === '('
1,148✔
1974
                        || $tokenType === ':'))
1,218✔
1975
                    ) {
1976
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,077✔
1977
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
1978
                        }
1979

1980
                        $newToken['code'] = T_INLINE_THEN;
1,077✔
1981
                        $newToken['type'] = 'T_INLINE_THEN';
1,077✔
1982

1983
                        $insideInlineIf[] = $stackPtr;
1,077✔
1984

1985
                        $finalTokens[$newStackPtr] = $newToken;
1,077✔
1986
                        $newStackPtr++;
1,077✔
1987
                        continue 2;
1,077✔
1988
                    }
1989

1990
                    break;
141✔
1991
                }//end for
1992

1993
                /*
1994
                 * This can still be a nullable type or a ternary.
1995
                 * Do additional checking.
1996
                 */
1997

1998
                $prevNonEmpty     = null;
162✔
1999
                $lastSeenNonEmpty = null;
162✔
2000

2001
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
162✔
2002
                    if (is_array($tokens[$i]) === true) {
162✔
2003
                        $tokenType = $tokens[$i][0];
162✔
2004
                    } else {
2005
                        $tokenType = $tokens[$i];
162✔
2006
                    }
2007

2008
                    if ($tokenType === T_STATIC
162✔
2009
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
108✔
2010
                        || $lastSeenNonEmpty === '(')
162✔
2011
                    ) {
2012
                        $lastSeenNonEmpty = $tokenType;
×
2013
                        continue;
×
2014
                    }
2015

2016
                    if ($prevNonEmpty === null
162✔
2017
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
162✔
2018
                    ) {
2019
                        // Found the previous non-empty token.
2020
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
162✔
2021
                            $newToken['code'] = T_NULLABLE;
135✔
2022
                            $newToken['type'] = 'T_NULLABLE';
135✔
2023

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

2028
                            break;
135✔
2029
                        }
2030

2031
                        $prevNonEmpty = $tokenType;
162✔
2032
                    }
2033

2034
                    if ($tokenType === T_FUNCTION
162✔
2035
                        || $tokenType === T_FN
162✔
2036
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
162✔
2037
                        || $tokenType === T_VAR
162✔
2038
                        || $tokenType === T_READONLY
162✔
2039
                    ) {
2040
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
141✔
2041
                            StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2042
                        }
2043

2044
                        $newToken['code'] = T_NULLABLE;
141✔
2045
                        $newToken['type'] = 'T_NULLABLE';
141✔
2046
                        break;
141✔
2047
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
162✔
2048
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
27✔
2049
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2050
                        }
2051

2052
                        $newToken['code'] = T_INLINE_THEN;
27✔
2053
                        $newToken['type'] = 'T_INLINE_THEN';
27✔
2054

2055
                        $insideInlineIf[] = $stackPtr;
27✔
2056
                        break;
27✔
2057
                    }
2058

2059
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
162✔
2060
                        $lastSeenNonEmpty = $tokenType;
162✔
2061
                    }
2062
                }//end for
2063

2064
                $finalTokens[$newStackPtr] = $newToken;
162✔
2065
                $newStackPtr++;
162✔
2066
                continue;
162✔
2067
            }//end if
2068

2069
            /*
2070
                Tokens after a double colon may look like scope openers,
2071
                such as when writing code like Foo::NAMESPACE, but they are
2072
                only ever variables or strings.
2073
            */
2074

2075
            if ($stackPtr > 1
3,009✔
2076
                && (is_array($tokens[($stackPtr - 1)]) === true
3,009✔
2077
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
3,009✔
2078
                && $tokenIsArray === true
3,009✔
2079
                && $token[0] !== T_STRING
3,009✔
2080
                && $token[0] !== T_VARIABLE
3,009✔
2081
                && $token[0] !== T_DOLLAR
3,009✔
2082
                && isset(Tokens::$emptyTokens[$token[0]]) === false
3,009✔
2083
            ) {
2084
                $newToken            = [];
×
2085
                $newToken['code']    = T_STRING;
×
2086
                $newToken['type']    = 'T_STRING';
×
2087
                $newToken['content'] = $token[1];
×
2088
                $finalTokens[$newStackPtr] = $newToken;
×
2089

2090
                $newStackPtr++;
×
2091
                continue;
×
2092
            }
2093

2094
            /*
2095
                Backfill the T_FN token for PHP versions < 7.4.
2096
            */
2097

2098
            if ($tokenIsArray === true
3,009✔
2099
                && $token[0] === T_STRING
3,009✔
2100
                && strtolower($token[1]) === 'fn'
3,009✔
2101
            ) {
2102
                // Modify the original token stack so that
2103
                // future checks (like looking for T_NULLABLE) can
2104
                // detect the T_FN token more easily.
2105
                $tokens[$stackPtr][0] = T_FN;
640✔
2106
                $token[0] = T_FN;
640✔
2107
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
640✔
2108
                    StatusWriter::write("* token $stackPtr changed from T_STRING to T_FN", 2);
×
2109
                }
2110
            }
2111

2112
            /*
2113
                This is a special condition for T_ARRAY tokens used for
2114
                function return types. We want to keep the parenthesis map clean,
2115
                so let's tag these tokens as T_STRING.
2116
            */
2117

2118
            if ($tokenIsArray === true
3,009✔
2119
                && ($token[0] === T_FUNCTION
3,009✔
2120
                || $token[0] === T_FN)
3,009✔
2121
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
3,009✔
2122
            ) {
2123
                // Go looking for the colon to start the return type hint.
2124
                // Start by finding the closing parenthesis of the function.
2125
                $parenthesisStack  = [];
2,340✔
2126
                $parenthesisCloser = false;
2,340✔
2127
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,340✔
2128
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,340✔
2129
                        $parenthesisStack[] = $x;
2,340✔
2130
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,340✔
2131
                        array_pop($parenthesisStack);
2,340✔
2132
                        if (empty($parenthesisStack) === true) {
2,340✔
2133
                            $parenthesisCloser = $x;
2,340✔
2134
                            break;
2,340✔
2135
                        }
2136
                    }
2137
                }
2138

2139
                if ($parenthesisCloser !== false) {
2,340✔
2140
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,340✔
2141
                        if (is_array($tokens[$x]) === false
2,340✔
2142
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
2,340✔
2143
                        ) {
2144
                            // Non-empty content.
2145
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,340✔
2146
                                // Found a use statement, so search ahead for the closing parenthesis.
2147
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
2148
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
2149
                                        continue(2);
57✔
2150
                                    }
2151
                                }
2152
                            }
2153

2154
                            break;
2,340✔
2155
                        }
2156
                    }
2157

2158
                    if (isset($tokens[$x]) === true
2,340✔
2159
                        && is_array($tokens[$x]) === false
2,340✔
2160
                        && $tokens[$x] === ':'
2,340✔
2161
                    ) {
2162
                        // Find the start of the return type.
2163
                        for ($x += 1; $x < $numTokens; $x++) {
1,950✔
2164
                            if (is_array($tokens[$x]) === true
1,950✔
2165
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
1,950✔
2166
                            ) {
2167
                                // Whitespace or comments before the return type.
2168
                                continue;
1,950✔
2169
                            }
2170

2171
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1,950✔
2172
                                // Found a nullable operator, so skip it.
2173
                                // But also convert the token to save the tokenizer
2174
                                // a bit of time later on.
2175
                                $tokens[$x] = [
135✔
2176
                                    T_NULLABLE,
135✔
2177
                                    '?',
135✔
2178
                                ];
90✔
2179

2180
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2181
                                    StatusWriter::write("* token $x changed from ? to T_NULLABLE", 2);
×
2182
                                }
2183

2184
                                continue;
135✔
2185
                            }
2186

2187
                            break;
1,950✔
2188
                        }//end for
2189
                    }//end if
2190
                }//end if
2191
            }//end if
2192

2193
            /*
2194
                PHP doesn't assign a token to goto labels, so we have to.
2195
                These are just string tokens with a single colon after them. Double
2196
                colons are already tokenized and so don't interfere with this check.
2197
                But we do have to account for CASE statements, that look just like
2198
                goto labels.
2199
            */
2200

2201
            if ($tokenIsArray === true
3,009✔
2202
                && $token[0] === T_STRING
3,009✔
2203
                && (is_array($tokens[($stackPtr - 1)]) === false
2,896✔
2204
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
3,009✔
2205
            ) {
2206
                // Find next non-empty token.
2207
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,670✔
2208
                    if (is_array($tokens[$i]) === true
2,670✔
2209
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,670✔
2210
                    ) {
2211
                        continue;
2,556✔
2212
                    }
2213

2214
                    break;
2,670✔
2215
                }
2216

2217
                if (isset($tokens[$i]) === true && $tokens[$i] === ':') {
2,670✔
2218
                    // Okay, so we have a colon, now we need to make sure that this is not
2219
                    // class constant access, a ternary, enum or switch case.
2220
                    $stopTokens = [
928✔
2221
                        T_CASE               => true,
1,392✔
2222
                        T_SEMICOLON          => true,
1,392✔
2223
                        T_OPEN_TAG           => true,
1,392✔
2224
                        T_OPEN_CURLY_BRACKET => true,
1,392✔
2225
                        T_INLINE_THEN        => true,
1,392✔
2226
                        T_ENUM               => true,
1,392✔
2227
                    ];
928✔
2228

2229
                    for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,392✔
2230
                        if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,392✔
2231
                            break;
1,392✔
2232
                        }
2233
                    }
2234

2235
                    if ($finalTokens[$x]['code'] !== T_CASE
1,392✔
2236
                        && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,392✔
2237
                        && $finalTokens[$x]['code'] !== T_ENUM
1,392✔
2238
                    ) {
2239
                        $finalTokens[$newStackPtr] = [
108✔
2240
                            'content' => $token[1],
108✔
2241
                            'code'    => T_GOTO_LABEL,
108✔
2242
                            'type'    => 'T_GOTO_LABEL',
108✔
2243
                        ];
72✔
2244

2245
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
108✔
NEW
2246
                            StatusWriter::write("* token $stackPtr changed from T_STRING to T_GOTO_LABEL", 2);
×
2247
                        }
2248

2249
                        // Modify the original token stack for the colon as potential
2250
                        // whitespace/comments between still needs to get the normal treatment.
2251
                        $tokens[$i] = [
108✔
2252
                            0 => T_GOTO_COLON,
108✔
2253
                            1 => ':',
108✔
2254
                        ];
72✔
2255

2256
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
108✔
NEW
2257
                            StatusWriter::write("* token $i changed from \":\" to T_GOTO_COLON in the original token stack", 2);
×
2258
                        }
2259

2260
                        $newStackPtr++;
108✔
2261
                        continue;
108✔
2262
                    }//end if
2263
                }//end if
2264
            }//end if
2265

2266
            /*
2267
                If this token has newlines in its content, split each line up
2268
                and create a new token for each line. We do this so it's easier
2269
                to ascertain where errors occur on a line.
2270
                Note that $token[1] is the token's content.
2271
            */
2272

2273
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
3,009✔
2274
                $tokenLines = explode($this->eolChar, $token[1]);
3,009✔
2275
                $numLines   = count($tokenLines);
3,009✔
2276
                $newToken   = [
2,006✔
2277
                    'type'    => Tokens::tokenName($token[0]),
3,009✔
2278
                    'code'    => $token[0],
3,009✔
2279
                    'content' => '',
3,009✔
2280
                ];
2,006✔
2281

2282
                for ($i = 0; $i < $numLines; $i++) {
3,009✔
2283
                    $newToken['content'] = $tokenLines[$i];
3,009✔
2284
                    if ($i === ($numLines - 1)) {
3,009✔
2285
                        if ($tokenLines[$i] === '') {
3,009✔
2286
                            break;
3,009✔
2287
                        }
2288
                    } else {
2289
                        $newToken['content'] .= $this->eolChar;
3,009✔
2290
                    }
2291

2292
                    $finalTokens[$newStackPtr] = $newToken;
3,009✔
2293
                    $newStackPtr++;
3,009✔
2294
                }
2295
            } else {
2296
                // Some T_STRING tokens should remain that way due to their context.
2297
                if ($tokenIsArray === true && $token[0] === T_STRING) {
3,009✔
2298
                    $preserveTstring = false;
2,670✔
2299

2300
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2301
                    // but the constant name should not be.
2302
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,670✔
2303
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,556✔
2304
                        || $insideConstDeclaration === true
2,670✔
2305
                    ) {
2306
                        // Find the next non-empty token.
2307
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,615✔
2308
                            if (is_array($tokens[$i]) === true
1,615✔
2309
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,615✔
2310
                            ) {
2311
                                continue;
1,480✔
2312
                            }
2313

2314
                            break;
1,615✔
2315
                        }
2316

2317
                        if ($tokens[$i] === '=') {
1,615✔
2318
                            $preserveTstring        = true;
1,426✔
2319
                            $insideConstDeclaration = false;
1,552✔
2320
                        }
2321
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,670✔
2322
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,670✔
2323
                    ) {
2324
                        $preserveTstring = true;
2,556✔
2325

2326
                        // Special case for syntax like: return new self/new parent
2327
                        // where self/parent should not be a string.
2328
                        $tokenContentLower = strtolower($token[1]);
2,556✔
2329
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,556✔
2330
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,556✔
2331
                        ) {
2332
                            $preserveTstring = false;
2,077✔
2333
                        }
2334
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,640✔
2335
                        // Function names for functions declared to return by reference.
2336
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,047✔
2337
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,047✔
2338
                                continue;
480✔
2339
                            }
2340

2341
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,047✔
2342
                                $preserveTstring = true;
480✔
2343
                            }
2344

2345
                            break;
1,047✔
2346
                        }
2347
                    } else {
2348
                        // Keywords with special PHPCS token when used as a function call.
2349
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,640✔
2350
                            if (is_array($tokens[$i]) === true
2,640✔
2351
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,640✔
2352
                            ) {
2353
                                continue;
2,235✔
2354
                            }
2355

2356
                            if ($tokens[$i][0] === '(') {
2,640✔
2357
                                $preserveTstring = true;
2,214✔
2358
                            }
2359

2360
                            break;
2,640✔
2361
                        }
2362
                    }//end if
2363

2364
                    if ($preserveTstring === true) {
2,670✔
2365
                        $finalTokens[$newStackPtr] = [
2,559✔
2366
                            'code'    => T_STRING,
2,559✔
2367
                            'type'    => 'T_STRING',
2,559✔
2368
                            'content' => $token[1],
2,559✔
2369
                        ];
1,706✔
2370

2371
                        $newStackPtr++;
2,559✔
2372
                        continue;
2,559✔
2373
                    }
2374
                }//end if
2375

2376
                $newToken = null;
3,009✔
2377
                if ($tokenIsArray === false) {
3,009✔
2378
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
2,997✔
2379
                        $newToken = self::$resolveTokenCache[$token[0]];
2,997✔
2380
                    }
2381
                } else {
2382
                    $cacheKey = null;
3,009✔
2383
                    if ($token[0] === T_STRING) {
3,009✔
2384
                        $cacheKey = strtolower($token[1]);
2,637✔
2385
                    } else if ($token[0] !== T_CURLY_OPEN) {
3,009✔
2386
                        $cacheKey = $token[0];
3,009✔
2387
                    }
2388

2389
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,009✔
2390
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,009✔
2391
                        $newToken['content'] = $token[1];
3,009✔
2392
                    }
2393
                }
2394

2395
                if ($newToken === null) {
3,009✔
2396
                    $newToken = self::standardiseToken($token);
18✔
2397
                }
2398

2399
                // Convert colons that are actually the ELSE component of an
2400
                // inline IF statement.
2401
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
3,009✔
2402
                    $isInlineIf = true;
1,077✔
2403

2404
                    // Make sure this isn't a named parameter label.
2405
                    // Get the previous non-empty token.
2406
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,077✔
2407
                        if (is_array($tokens[$i]) === false
1,077✔
2408
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,077✔
2409
                        ) {
2410
                            break;
1,077✔
2411
                        }
2412
                    }
2413

2414
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,077✔
2415
                        $isInlineIf = false;
639✔
2416
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2417
                            StatusWriter::write('* token is parameter label, not T_INLINE_ELSE', 2);
×
2418
                        }
2419
                    }
2420

2421
                    if ($isInlineIf === true) {
1,077✔
2422
                        // Make sure this isn't a return type separator.
2423
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,077✔
2424
                            if (is_array($tokens[$i]) === false
1,077✔
2425
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,077✔
2426
                                && $tokens[$i][0] !== T_COMMENT
1,077✔
2427
                                && $tokens[$i][0] !== T_WHITESPACE)
1,077✔
2428
                            ) {
2429
                                break;
1,077✔
2430
                            }
2431
                        }
2432

2433
                        if ($tokens[$i] === ')') {
1,077✔
2434
                            $parenCount = 1;
639✔
2435
                            for ($i--; $i > 0; $i--) {
639✔
2436
                                if ($tokens[$i] === '(') {
639✔
2437
                                    $parenCount--;
639✔
2438
                                    if ($parenCount === 0) {
639✔
2439
                                        break;
639✔
2440
                                    }
2441
                                } else if ($tokens[$i] === ')') {
639✔
2442
                                    $parenCount++;
×
2443
                                }
2444
                            }
2445

2446
                            // We've found the open parenthesis, so if the previous
2447
                            // non-empty token is FUNCTION or USE, this is a return type.
2448
                            // Note that we need to skip T_STRING tokens here as these
2449
                            // can be function names.
2450
                            for ($i--; $i > 0; $i--) {
639✔
2451
                                if (is_array($tokens[$i]) === false
639✔
2452
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
639✔
2453
                                    && $tokens[$i][0] !== T_COMMENT
639✔
2454
                                    && $tokens[$i][0] !== T_WHITESPACE
639✔
2455
                                    && $tokens[$i][0] !== T_STRING)
639✔
2456
                                ) {
2457
                                    break;
639✔
2458
                                }
2459
                            }
2460

2461
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
639✔
2462
                                $isInlineIf = false;
639✔
2463
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2464
                                    StatusWriter::write('* token is return type, not T_INLINE_ELSE', 2);
×
2465
                                }
2466
                            }
2467
                        }//end if
2468
                    }//end if
2469

2470
                    // Check to see if this is a CASE or DEFAULT opener.
2471
                    if ($isInlineIf === true) {
1,077✔
2472
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,077✔
2473
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,077✔
2474
                            if (is_array($tokens[$i]) === true
1,077✔
2475
                                && ($tokens[$i][0] === T_CASE
1,077✔
2476
                                || $tokens[$i][0] === T_DEFAULT)
1,077✔
2477
                            ) {
2478
                                $isInlineIf = false;
×
2479
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2480
                                    StatusWriter::write('* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE', 2);
×
2481
                                }
2482

2483
                                break;
×
2484
                            }
2485

2486
                            if (is_array($tokens[$i]) === false
1,077✔
2487
                                && ($tokens[$i] === ';'
1,077✔
2488
                                || $tokens[$i] === '{'
1,077✔
2489
                                || $tokens[$i] === '}')
1,077✔
2490
                            ) {
2491
                                break;
822✔
2492
                            }
2493
                        }//end for
2494
                    }//end if
2495

2496
                    if ($isInlineIf === true) {
1,077✔
2497
                        array_pop($insideInlineIf);
1,077✔
2498
                        $newToken['code'] = T_INLINE_ELSE;
1,077✔
2499
                        $newToken['type'] = 'T_INLINE_ELSE';
1,077✔
2500

2501
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,077✔
2502
                            StatusWriter::write('* token changed from T_COLON to T_INLINE_ELSE', 2);
×
2503
                        }
2504
                    }
2505
                }//end if
2506

2507
                // This is a special condition for T_ARRAY tokens used for anything else
2508
                // but array declarations, like type hinting function arguments as
2509
                // being arrays.
2510
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2511
                // T_STRING.
2512
                if ($newToken['code'] === T_ARRAY) {
3,009✔
2513
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,518✔
2514
                        if (is_array($tokens[$i]) === false
1,518✔
2515
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,518✔
2516
                        ) {
2517
                            // Non-empty content.
2518
                            break;
1,518✔
2519
                        }
2520
                    }
2521

2522
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,518✔
2523
                        $newToken['code'] = T_STRING;
1,287✔
2524
                        $newToken['type'] = 'T_STRING';
1,287✔
2525
                    }
2526
                }
2527

2528
                // This is a special case for PHP 5.6 use function and use const
2529
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2530
                // and T_CONST.
2531
                if (($newToken['code'] === T_FUNCTION
3,009✔
2532
                    || $newToken['code'] === T_CONST)
3,009✔
2533
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
3,009✔
2534
                ) {
2535
                    $newToken['code'] = T_STRING;
135✔
2536
                    $newToken['type'] = 'T_STRING';
135✔
2537
                }
2538

2539
                // This is a special case for use groups in PHP 7+ where leaving
2540
                // the curly braces as their normal tokens would confuse
2541
                // the scope map and sniffs.
2542
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
3,009✔
2543
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
3,009✔
2544
                ) {
2545
                    $newToken['code'] = T_OPEN_USE_GROUP;
135✔
2546
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
135✔
2547
                    $insideUseGroup   = true;
135✔
2548
                }
2549

2550
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,009✔
2551
                    $newToken['code'] = T_CLOSE_USE_GROUP;
135✔
2552
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
135✔
2553
                    $insideUseGroup   = false;
135✔
2554
                }
2555

2556
                $finalTokens[$newStackPtr] = $newToken;
3,009✔
2557
                $newStackPtr++;
3,009✔
2558
            }//end if
2559
        }//end for
2560

2561
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,009✔
2562
            StatusWriter::write('*** END PHP TOKENIZING ***', 1);
×
2563
        }
2564

2565
        return $finalTokens;
3,009✔
2566

2567
    }//end tokenize()
2568

2569

2570
    /**
2571
     * Performs additional processing after main tokenizing.
2572
     *
2573
     * This additional processing checks for CASE statements that are using curly
2574
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2575
     * into T_CLOSURE when they are not standard function definitions. It also
2576
     * detects short array syntax and converts those square brackets into new tokens.
2577
     * It also corrects some usage of the static and class keywords. It also
2578
     * assigns tokens to function return types.
2579
     *
2580
     * @return void
2581
     */
2582
    protected function processAdditional()
1,707✔
2583
    {
2584
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,707✔
2585
            StatusWriter::write('*** START ADDITIONAL PHP PROCESSING ***', 1);
×
2586
        }
2587

2588
        $this->createAttributesNestingMap();
1,707✔
2589

2590
        $numTokens         = count($this->tokens);
1,707✔
2591
        $lastSeenTypeToken = $numTokens;
1,707✔
2592

2593
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,707✔
2594
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2595
            if (isset($this->tokens[$i]['scope_opener']) === true
1,707✔
2596
                && isset($this->tokens[$i]['scope_condition']) === false
1,707✔
2597
            ) {
2598
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2599
            }
2600

2601
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,707✔
2602
                /*
2603
                    Detect functions that are actually closures and
2604
                    assign them a different token.
2605
                */
2606

2607
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,428✔
2608
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,416✔
2609
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,416✔
2610
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,416✔
2611
                        ) {
2612
                            break;
1,416✔
2613
                        }
2614
                    }
2615

2616
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,416✔
2617
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,053✔
2618
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,053✔
2619
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,053✔
2620
                            $line = $this->tokens[$i]['line'];
×
2621
                            StatusWriter::write("* token $i on line $line changed from T_FUNCTION to T_CLOSURE", 1);
×
2622
                        }
2623

2624
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,053✔
2625
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
642✔
2626
                                continue;
×
2627
                            }
2628

2629
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
642✔
2630
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
642✔
2631
                                $type = $this->tokens[$x]['type'];
×
2632
                                StatusWriter::write("* cleaned $x ($type) *", 2);
×
2633
                            }
2634
                        }
2635
                    }
2636
                }//end if
2637

2638
                continue;
1,428✔
2639
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,707✔
2640
                /*
2641
                    Detect anonymous classes and assign them a different token.
2642
                */
2643

2644
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,470✔
2645
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,470✔
2646
                        break;
1,470✔
2647
                    }
2648
                }
2649

2650
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,470✔
2651
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,470✔
2652
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,443✔
2653
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,470✔
2654
                ) {
2655
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,068✔
2656
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,068✔
2657
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,068✔
2658
                        $line = $this->tokens[$i]['line'];
×
2659
                        StatusWriter::write("* token $i on line $line changed from T_CLASS to T_ANON_CLASS", 1);
×
2660
                    }
2661

2662
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,068✔
2663
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,068✔
2664
                    ) {
2665
                        $closer = $this->tokens[$x]['parenthesis_closer'];
657✔
2666

2667
                        $this->tokens[$i]['parenthesis_opener']     = $x;
657✔
2668
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
657✔
2669
                        $this->tokens[$i]['parenthesis_owner']      = $i;
657✔
2670
                        $this->tokens[$x]['parenthesis_owner']      = $i;
657✔
2671
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
657✔
2672

2673
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
657✔
2674
                            $line = $this->tokens[$i]['line'];
×
2675
                            StatusWriter::write("* added parenthesis keys to T_ANON_CLASS token $i on line $line", 2);
×
2676
                        }
2677
                    }
2678

2679
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,068✔
2680
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,068✔
2681
                            continue;
×
2682
                        }
2683

2684
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,068✔
2685
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,068✔
2686
                            $type = $this->tokens[$x]['type'];
×
2687
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
2688
                        }
2689
                    }
2690
                }//end if
2691

2692
                continue;
1,470✔
2693
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,707✔
2694
                // Possible arrow function.
2695
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,137✔
2696
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,137✔
2697
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,137✔
2698
                    ) {
2699
                        // Non-whitespace content.
2700
                        break;
1,137✔
2701
                    }
2702
                }
2703

2704
                if (isset($this->tokens[$x]) === true
1,137✔
2705
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,137✔
2706
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,137✔
2707
                ) {
2708
                    $ignore  = Tokens::$emptyTokens;
1,134✔
2709
                    $ignore += [
756✔
2710
                        T_ARRAY                  => T_ARRAY,
1,134✔
2711
                        T_CALLABLE               => T_CALLABLE,
1,134✔
2712
                        T_COLON                  => T_COLON,
1,134✔
2713
                        T_NAMESPACE              => T_NAMESPACE,
1,134✔
2714
                        T_NS_SEPARATOR           => T_NS_SEPARATOR,
1,134✔
2715
                        T_NULL                   => T_NULL,
1,134✔
2716
                        T_TRUE                   => T_TRUE,
1,134✔
2717
                        T_FALSE                  => T_FALSE,
1,134✔
2718
                        T_NULLABLE               => T_NULLABLE,
1,134✔
2719
                        T_PARENT                 => T_PARENT,
1,134✔
2720
                        T_SELF                   => T_SELF,
1,134✔
2721
                        T_STATIC                 => T_STATIC,
1,134✔
2722
                        T_STRING                 => T_STRING,
1,134✔
2723
                        T_TYPE_UNION             => T_TYPE_UNION,
1,134✔
2724
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,134✔
2725
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,134✔
2726
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,134✔
2727
                    ];
756✔
2728

2729
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,134✔
2730
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,134✔
2731
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,134✔
2732
                            break;
1,134✔
2733
                        }
2734
                    }
2735

2736
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,134✔
2737
                        $endTokens = [
756✔
2738
                            T_COLON                => true,
1,134✔
2739
                            T_COMMA                => true,
1,134✔
2740
                            T_SEMICOLON            => true,
1,134✔
2741
                            T_CLOSE_PARENTHESIS    => true,
1,134✔
2742
                            T_CLOSE_SQUARE_BRACKET => true,
1,134✔
2743
                            T_CLOSE_CURLY_BRACKET  => true,
1,134✔
2744
                            T_CLOSE_SHORT_ARRAY    => true,
1,134✔
2745
                            T_OPEN_TAG             => true,
1,134✔
2746
                            T_CLOSE_TAG            => true,
1,134✔
2747
                        ];
756✔
2748

2749
                        $inTernary    = false;
1,134✔
2750
                        $lastEndToken = null;
1,134✔
2751

2752
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,134✔
2753
                            // Arrow function closer should never be shared with the closer of a match
2754
                            // control structure.
2755
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,134✔
2756
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,134✔
2757
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,134✔
2758
                            ) {
2759
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
423✔
2760
                                    // Match in return value of arrow function. Move on to the next token.
2761
                                    continue;
423✔
2762
                                }
2763

2764
                                // Arrow function as return value for the last match case without trailing comma.
2765
                                if ($lastEndToken !== null) {
423✔
2766
                                    $scopeCloser = $lastEndToken;
423✔
2767
                                    break;
423✔
2768
                                }
2769

2770
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2771
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2772
                                        $scopeCloser = $lastNonEmpty;
186✔
2773
                                        break 2;
186✔
2774
                                    }
2775
                                }
2776
                            }
2777

2778
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,134✔
2779
                                if ($lastEndToken !== null
1,134✔
2780
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
997✔
2781
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
818✔
2782
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
997✔
2783
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
1,134✔
2784
                                ) {
2785
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2786
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2787
                                            $scopeCloser = $lastNonEmpty;
186✔
2788
                                            break;
186✔
2789
                                        }
2790
                                    }
2791
                                }
2792

2793
                                break;
1,134✔
2794
                            }
2795

2796
                            if ($inTernary === false
1,134✔
2797
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,134✔
2798
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,134✔
2799
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,134✔
2800
                            ) {
2801
                                // Found a nested arrow function that already has the closer set and is in
2802
                                // the same scope as us, so we can use its closer.
2803
                                break;
186✔
2804
                            }
2805

2806
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,134✔
2807
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,134✔
2808
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,134✔
2809
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,134✔
2810
                            ) {
2811
                                // We minus 1 here in case the closer can be shared with us.
2812
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
423✔
2813
                                continue;
423✔
2814
                            }
2815

2816
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,134✔
2817
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
723✔
2818
                                $lastEndToken = $scopeCloser;
723✔
2819
                                continue;
723✔
2820
                            }
2821

2822
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,134✔
2823
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
186✔
2824
                                $lastEndToken = $scopeCloser;
186✔
2825
                                continue;
186✔
2826
                            }
2827

2828
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,134✔
2829
                                $inTernary = true;
186✔
2830
                                continue;
186✔
2831
                            }
2832

2833
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,134✔
2834
                                if ($inTernary === false) {
186✔
2835
                                    break;
186✔
2836
                                }
2837

2838
                                $inTernary = false;
186✔
2839
                                continue;
186✔
2840
                            }
2841
                        }//end for
2842

2843
                        if ($scopeCloser !== $numTokens) {
1,134✔
2844
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,134✔
2845
                                $line = $this->tokens[$i]['line'];
×
2846
                                StatusWriter::write("=> token $i on line $line processed as arrow function", 1);
×
2847
                                StatusWriter::write("* scope opener set to $arrow *", 2);
×
2848
                                StatusWriter::write("* scope closer set to $scopeCloser *", 2);
×
2849
                                StatusWriter::write("* parenthesis opener set to $x *", 2);
×
2850
                                StatusWriter::write("* parenthesis closer set to $closer *", 2);
×
2851
                            }
2852

2853
                            $this->tokens[$i]['code']            = T_FN;
1,134✔
2854
                            $this->tokens[$i]['type']            = 'T_FN';
1,134✔
2855
                            $this->tokens[$i]['scope_condition'] = $i;
1,134✔
2856
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,134✔
2857
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,134✔
2858
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,134✔
2859
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,134✔
2860
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,134✔
2861

2862
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,134✔
2863
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,134✔
2864

2865
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,134✔
2866
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,134✔
2867
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,134✔
2868
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,134✔
2869
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,134✔
2870
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,134✔
2871

2872
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,134✔
2873
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,134✔
2874
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,134✔
2875
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,134✔
2876

2877
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,134✔
2878
                                $line = $this->tokens[$arrow]['line'];
×
2879
                                StatusWriter::write("* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW", 2);
×
2880
                            }
2881
                        }//end if
2882
                    }//end if
2883
                }//end if
2884

2885
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
2886
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,137✔
2887
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
600✔
2888
                        $line = $this->tokens[$i]['line'];
×
2889
                        StatusWriter::write("=> token $i on line $line is not an arrow function", 1);
×
2890
                        StatusWriter::write('* token changed from T_FN to T_STRING', 2);
×
2891
                    }
2892

2893
                    $this->tokens[$i]['code'] = T_STRING;
600✔
2894
                    $this->tokens[$i]['type'] = 'T_STRING';
958✔
2895
                }
2896
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,707✔
2897
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,164✔
2898
                    continue;
99✔
2899
                }
2900

2901
                // Unless there is a variable or a bracket before this token,
2902
                // it is the start of an array being defined using the short syntax.
2903
                $isShortArray = false;
1,164✔
2904
                $allowed      = [
776✔
2905
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,164✔
2906
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,164✔
2907
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,164✔
2908
                    T_VARIABLE                 => T_VARIABLE,
1,164✔
2909
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,164✔
2910
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,164✔
2911
                    T_STRING                   => T_STRING,
1,164✔
2912
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,164✔
2913
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,164✔
2914
                ];
776✔
2915
                $allowed     += Tokens::$magicConstants;
1,164✔
2916

2917
                for ($x = ($i - 1); $x >= 0; $x--) {
1,164✔
2918
                    // If we hit a scope opener, the statement has ended
2919
                    // without finding anything, so it's probably an array
2920
                    // using PHP 7.1 short list syntax.
2921
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,164✔
2922
                        $isShortArray = true;
234✔
2923
                        break;
234✔
2924
                    }
2925

2926
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,164✔
2927
                        // Allow for control structures without braces.
2928
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,164✔
2929
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,164✔
2930
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
99✔
2931
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,164✔
2932
                        ) {
2933
                            $isShortArray = true;
1,164✔
2934
                        }
2935

2936
                        break;
1,164✔
2937
                    }
2938
                }//end for
2939

2940
                if ($isShortArray === true) {
1,164✔
2941
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,164✔
2942
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,164✔
2943

2944
                    $closer = $this->tokens[$i]['bracket_closer'];
1,164✔
2945
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,164✔
2946
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,164✔
2947
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,164✔
2948
                        $line = $this->tokens[$i]['line'];
×
2949
                        StatusWriter::write("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
2950
                        $line = $this->tokens[$closer]['line'];
×
2951
                        StatusWriter::write("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
2952
                    }
2953
                }
2954

2955
                continue;
1,164✔
2956
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,707✔
2957
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
777✔
2958
                    // Not a match expression after all.
2959
                    $this->tokens[$i]['code'] = T_STRING;
102✔
2960
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
2961

2962
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
2963
                        StatusWriter::write("* token $i changed from T_MATCH to T_STRING", 2);
×
2964
                    }
2965

2966
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
2967
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
2968
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
2969
                        unset(
68✔
2970
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
2971
                            $this->tokens[$closer]['parenthesis_owner']
102✔
2972
                        );
68✔
2973
                        unset(
68✔
2974
                            $this->tokens[$i]['parenthesis_opener'],
102✔
2975
                            $this->tokens[$i]['parenthesis_closer'],
102✔
2976
                            $this->tokens[$i]['parenthesis_owner']
102✔
2977
                        );
68✔
2978

2979
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
2980
                            StatusWriter::write("* cleaned parenthesis of token $i *", 2);
68✔
2981
                        }
2982
                    }
2983
                } else {
2984
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
2985
                    $searchFor  = [
518✔
2986
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
777✔
2987
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
777✔
2988
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
777✔
2989
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
777✔
2990
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
777✔
2991
                    ];
518✔
2992
                    $searchFor += Tokens::$scopeOpeners;
777✔
2993

2994
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
777✔
2995
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
777✔
2996
                            continue;
777✔
2997
                        }
2998

2999
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
477✔
3000
                            $x = $this->tokens[$x]['scope_closer'];
291✔
3001
                            continue;
291✔
3002
                        }
3003

3004
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
477✔
3005
                            $x = $this->tokens[$x]['parenthesis_closer'];
477✔
3006
                            continue;
477✔
3007
                        }
3008

3009
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
477✔
3010
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
3011
                            continue;
291✔
3012
                        }
3013

3014
                        // This must be a double arrow, but make sure anyhow.
3015
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
477✔
3016
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
477✔
3017
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
477✔
3018

3019
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
477✔
3020
                                StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
3021
                            }
3022
                        }
3023
                    }//end for
3024
                }//end if
3025

3026
                continue;
777✔
3027
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,707✔
3028
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,707✔
3029
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,707✔
3030
            ) {
3031
                if ($lastSeenTypeToken < $i) {
1,704✔
3032
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3033
                    // No need to do it again.
3034
                    continue;
849✔
3035
                }
3036

3037
                /*
3038
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3039
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3040
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3041

3042
                    All type related tokens will be converted in one go as soon as this section is hit.
3043
                */
3044

3045
                $allowed = [
1,136✔
3046
                    T_STRING       => T_STRING,
1,704✔
3047
                    T_CALLABLE     => T_CALLABLE,
1,704✔
3048
                    T_SELF         => T_SELF,
1,704✔
3049
                    T_PARENT       => T_PARENT,
1,704✔
3050
                    T_STATIC       => T_STATIC,
1,704✔
3051
                    T_FALSE        => T_FALSE,
1,704✔
3052
                    T_TRUE         => T_TRUE,
1,704✔
3053
                    T_NULL         => T_NULL,
1,704✔
3054
                    T_NAMESPACE    => T_NAMESPACE,
1,704✔
3055
                    T_NS_SEPARATOR => T_NS_SEPARATOR,
1,704✔
3056
                ];
1,136✔
3057

3058
                $suspectedType       = null;
1,704✔
3059
                $typeTokenCountAfter = 0;
1,704✔
3060

3061
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,704✔
3062
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,704✔
3063
                        continue;
1,704✔
3064
                    }
3065

3066
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,704✔
3067
                        ++$typeTokenCountAfter;
1,275✔
3068
                        continue;
1,275✔
3069
                    }
3070

3071
                    if (($typeTokenCountAfter > 0
1,704✔
3072
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,704✔
3073
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,704✔
3074
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,700✔
3075
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,704✔
3076
                    ) {
3077
                        // Skip past reference and variadic indicators for parameter types.
3078
                        continue;
912✔
3079
                    }
3080

3081
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,704✔
3082
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3083
                        $suspectedType = 'property or parameter';
1,098✔
3084
                        break;
1,098✔
3085
                    }
3086

3087
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,704✔
3088
                        // Possible arrow function.
3089
                        $suspectedType = 'return';
1,134✔
3090
                        break;
1,134✔
3091
                    }
3092

3093
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,704✔
3094
                        // Possible abstract method or interface method.
3095
                        $suspectedType = 'return';
1,629✔
3096
                        break;
1,629✔
3097
                    }
3098

3099
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,704✔
3100
                        && isset($this->tokens[$x]['scope_condition']) === true
1,704✔
3101
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,704✔
3102
                    ) {
3103
                        $suspectedType = 'return';
1,416✔
3104
                        break;
1,416✔
3105
                    }
3106

3107
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,704✔
3108
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3109
                        $suspectedType = 'constant';
1,059✔
3110
                        break;
1,059✔
3111
                    }
3112

3113
                    break;
1,704✔
3114
                }//end for
3115

3116
                if (($typeTokenCountAfter === 0
1,704✔
3117
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,704✔
3118
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,704✔
3119
                    || isset($suspectedType) === false
1,704✔
3120
                ) {
3121
                    // Definitely not a union, intersection or DNF type, move on.
3122
                    continue;
1,704✔
3123
                }
3124

3125
                if ($suspectedType === 'property or parameter') {
1,665✔
3126
                    unset($allowed[T_STATIC]);
1,098✔
3127
                }
3128

3129
                $typeTokenCountBefore = 0;
1,665✔
3130
                $typeOperators        = [$i];
1,665✔
3131
                $parenthesesCount     = 0;
1,665✔
3132
                $confirmed            = false;
1,665✔
3133
                $maybeNullable        = null;
1,665✔
3134

3135
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,665✔
3136
                    ++$parenthesesCount;
1,665✔
3137
                }
3138

3139
                for ($x = ($i - 1); $x >= 0; $x--) {
1,665✔
3140
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,665✔
3141
                        continue;
1,377✔
3142
                    }
3143

3144
                    if ($suspectedType === 'property or parameter'
1,665✔
3145
                        && $this->tokens[$x]['code'] === T_STRING
1,665✔
3146
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,665✔
3147
                    ) {
3148
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3149
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3150
                        $this->tokens[$x]['code'] = T_STATIC;
300✔
3151
                        $this->tokens[$x]['type'] = 'T_STATIC';
300✔
3152

3153
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3154
                            $line = $this->tokens[$x]['line'];
×
3155
                            StatusWriter::write("* token $x on line $line changed back from T_STRING to T_STATIC", 1);
×
3156
                        }
3157
                    }
3158

3159
                    if ($suspectedType === 'property or parameter'
1,665✔
3160
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,665✔
3161
                    ) {
3162
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3163
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3164
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,098✔
3165
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,098✔
3166
                        ) {
3167
                            $confirmed = true;
1,098✔
3168
                            break;
1,098✔
3169
                        } else {
3170
                            // This may still be an arrow function which hasn't been handled yet.
3171
                            for ($y = ($x - 1); $y > 0; $y--) {
1,098✔
3172
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
1,098✔
3173
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,098✔
3174
                                ) {
3175
                                    // Non-whitespace content.
3176
                                    break;
1,098✔
3177
                                }
3178
                            }
3179

3180
                            if ($this->tokens[$y]['code'] === T_FN) {
1,098✔
3181
                                $confirmed = true;
897✔
3182
                                break;
897✔
3183
                            }
3184
                        }
3185
                    }//end if
3186

3187
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,665✔
3188
                        ++$typeTokenCountBefore;
1,377✔
3189
                        continue;
1,377✔
3190
                    }
3191

3192
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3193
                    if (($typeTokenCountBefore > 0
1,665✔
3194
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,665✔
3195
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,569✔
3196
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,665✔
3197
                    ) {
3198
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
888✔
3199
                            $maybeNullable = $x;
300✔
3200
                        }
3201

3202
                        continue;
888✔
3203
                    }
3204

3205
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,665✔
3206
                        $typeOperators[] = $x;
1,275✔
3207
                        continue;
1,275✔
3208
                    }
3209

3210
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,665✔
3211
                        ++$parenthesesCount;
1,377✔
3212
                        $typeOperators[] = $x;
1,377✔
3213
                        continue;
1,377✔
3214
                    }
3215

3216
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,665✔
3217
                        // Make sure this is the colon for a return type.
3218
                        for ($y = ($x - 1); $y > 0; $y--) {
933✔
3219
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
933✔
3220
                                break;
933✔
3221
                            }
3222
                        }
3223

3224
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
933✔
3225
                            // Definitely not a union, intersection or DNF return type, move on.
3226
                            continue 2;
300✔
3227
                        }
3228

3229
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
933✔
3230
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
897✔
3231
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
300✔
3232
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
897✔
3233
                            ) {
3234
                                $confirmed = true;
897✔
3235
                            }
3236

3237
                            break;
897✔
3238
                        }
3239

3240
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3241
                        // Closure use tokens won't be parentheses owners until PHPCS 4.0.
3242
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
933✔
3243
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
897✔
3244
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
897✔
3245
                                    break;
897✔
3246
                                }
3247
                            }
3248

3249
                            if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
897✔
3250
                                $confirmed = true;
897✔
3251
                            }
3252
                        }
3253

3254
                        break;
933✔
3255
                    }//end if
3256

3257
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,665✔
3258
                        $confirmed = true;
924✔
3259
                        break;
924✔
3260
                    }
3261

3262
                    if ($suspectedType === 'property or parameter'
1,665✔
3263
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,414✔
3264
                        || $this->tokens[$x]['code'] === T_VAR
1,402✔
3265
                        || $this->tokens[$x]['code'] === T_STATIC
1,402✔
3266
                        || $this->tokens[$x]['code'] === T_READONLY
1,402✔
3267
                        || $this->tokens[$x]['code'] === T_FINAL)
1,665✔
3268
                    ) {
3269
                        // This will also confirm constructor property promotion parameters, but that's fine.
3270
                        $confirmed = true;
912✔
3271
                    }
3272

3273
                    break;
1,665✔
3274
                }//end for
3275

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

3279
                if ($confirmed === false
1,665✔
3280
                    && $suspectedType === 'property or parameter'
1,665✔
3281
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,665✔
3282
                ) {
3283
                    $parens = $this->tokens[$i]['nested_parenthesis'];
711✔
3284
                    $last   = end($parens);
711✔
3285

3286
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
711✔
3287
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
711✔
3288
                    ) {
3289
                        $confirmed = true;
711✔
3290
                    } else {
3291
                        // No parenthesis owner set, this may be an arrow function which has not yet
3292
                        // had additional processing done.
3293
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
213✔
3294
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
213✔
3295
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
213✔
3296
                                    continue;
213✔
3297
                                }
3298

3299
                                break;
213✔
3300
                            }
3301

3302
                            if ($this->tokens[$x]['code'] === T_FN) {
213✔
3303
                                for (--$x; $x >= 0; $x--) {
×
3304
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
3305
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3306
                                    ) {
3307
                                        continue;
×
3308
                                    }
3309

3310
                                    break;
×
3311
                                }
3312

3313
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3314
                                    $confirmed = true;
×
3315
                                }
3316
                            }
3317
                        }//end if
3318
                    }//end if
3319

3320
                    unset($parens, $last);
711✔
3321
                }//end if
3322

3323
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,665✔
3324
                    // Not a (valid) union, intersection or DNF type after all, move on.
3325
                    continue;
1,665✔
3326
                }
3327

3328
                foreach ($typeOperators as $x) {
1,239✔
3329
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,239✔
3330
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,239✔
3331
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,239✔
3332

3333
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3334
                            $line = $this->tokens[$x]['line'];
×
3335
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
826✔
3336
                        }
3337
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,239✔
3338
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,239✔
3339
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,239✔
3340

3341
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3342
                            $line = $this->tokens[$x]['line'];
×
3343
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
826✔
3344
                        }
3345
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,239✔
3346
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,239✔
3347
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,239✔
3348

3349
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3350
                            $line = $this->tokens[$x]['line'];
×
3351
                            StatusWriter::write("* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS", 1);
826✔
3352
                        }
3353
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,239✔
3354
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,239✔
3355
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,239✔
3356

3357
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3358
                            $line = $this->tokens[$x]['line'];
×
3359
                            StatusWriter::write("* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS", 1);
×
3360
                        }
3361
                    }//end if
3362
                }//end foreach
3363

3364
                if (isset($maybeNullable) === true) {
1,239✔
3365
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
300✔
3366
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
300✔
3367

3368
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3369
                        $line = $this->tokens[$maybeNullable]['line'];
×
3370
                        StatusWriter::write("* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE", 1);
×
3371
                    }
3372
                }
3373

3374
                continue;
1,239✔
3375
            } else if ($this->tokens[$i]['code'] === T_STATIC) {
1,707✔
3376
                for ($x = ($i - 1); $x > 0; $x--) {
1,344✔
3377
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,344✔
3378
                        break;
1,344✔
3379
                    }
3380
                }
3381

3382
                if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
1,344✔
3383
                    $this->tokens[$i]['code'] = T_STRING;
×
3384
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3385

3386
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3387
                        $line = $this->tokens[$i]['line'];
×
3388
                        StatusWriter::write("* token $i on line $line changed from T_STATIC to T_STRING", 1);
×
3389
                    }
3390
                }
3391

3392
                continue;
1,344✔
3393
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,707✔
3394
                || $this->tokens[$i]['code'] === T_FALSE
1,707✔
3395
                || $this->tokens[$i]['code'] === T_NULL
1,707✔
3396
            ) {
3397
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,623✔
3398
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,623✔
3399
                        // Non-whitespace content.
3400
                        break;
1,623✔
3401
                    }
3402
                }
3403

3404
                if ($x !== $numTokens
1,623✔
3405
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,623✔
3406
                ) {
3407
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3408
                        $line = $this->tokens[$i]['line'];
×
3409
                        $type = $this->tokens[$i]['type'];
×
3410
                        StatusWriter::write("* token $i on line $line changed from $type to T_STRING", 1);
×
3411
                    }
3412

3413
                    $this->tokens[$i]['code'] = T_STRING;
×
3414
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3415
                }
3416
            }//end if
3417

3418
            if (($this->tokens[$i]['code'] !== T_CASE
1,707✔
3419
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,707✔
3420
                || isset($this->tokens[$i]['scope_opener']) === false
1,707✔
3421
            ) {
3422
                // Only interested in CASE and DEFAULT statements from here on in.
3423
                continue;
1,707✔
3424
            }
3425

3426
            $scopeOpener = $this->tokens[$i]['scope_opener'];
456✔
3427
            $scopeCloser = $this->tokens[$i]['scope_closer'];
456✔
3428

3429
            // If the first char after the opener is a curly brace
3430
            // and that brace has been ignored, it is actually
3431
            // opening this case statement and the opener and closer are
3432
            // probably set incorrectly.
3433
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
456✔
3434
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
456✔
3435
                    // Non-whitespace content.
3436
                    break;
456✔
3437
                }
3438
            }
3439

3440
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
456✔
3441
                // Special case for multiple CASE statements that share the same
3442
                // closer. Because we are going backwards through the file, this next
3443
                // CASE statement is already fixed, so just use its closer and don't
3444
                // worry about fixing anything.
3445
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3446
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3447
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3448
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3449
                    $newType = $this->tokens[$newCloser]['type'];
×
3450
                    $line    = $this->tokens[$i]['line'];
×
3451
                    StatusWriter::write("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3452
                }
3453

3454
                continue;
×
3455
            }
3456

3457
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
456✔
3458
                || isset($this->tokens[$x]['scope_condition']) === true
456✔
3459
            ) {
3460
                // Not a CASE/DEFAULT with a curly brace opener.
3461
                continue;
456✔
3462
            }
3463

3464
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3465
            // not whatever it already is. The opener needs to be the opening curly
3466
            // brace so everything matches up.
3467
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3468
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3469
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3470
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3471
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3472
            }
3473

3474
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3475
                $line      = $this->tokens[$i]['line'];
×
3476
                $tokenType = $this->tokens[$i]['type'];
×
3477

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

3482
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3483
                $newType = $this->tokens[$newCloser]['type'];
×
3484
                StatusWriter::write("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3485
            }
3486

3487
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3488
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3489
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3490
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3491
            }
3492

3493
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3494
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3495
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3496
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3497
            } else {
3498
                // We were using a shared closer. All tokens that were
3499
                // sharing this closer with us, except for the scope condition
3500
                // and it's opener, need to now point to the new closer.
3501
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3502
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3503
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3504
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3505
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3506
                    ) {
3507
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3508

3509
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3510
                            $line      = $this->tokens[$y]['line'];
×
3511
                            $tokenType = $this->tokens[$y]['type'];
×
3512
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3513
                            $newType   = $this->tokens[$newCloser]['type'];
×
3514
                            StatusWriter::write("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3515
                        }
3516
                    }
3517
                }
3518
            }//end if
3519

3520
            unset($this->tokens[$x]['bracket_opener']);
54✔
3521
            unset($this->tokens[$x]['bracket_closer']);
54✔
3522
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3523
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3524
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3525

3526
            // Now fix up all the tokens that think they are
3527
            // inside the CASE/DEFAULT statement when they are really outside.
3528
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3529
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3530
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3531
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3532
                        unset($this->tokens[$x]['conditions'][$num]);
×
3533

3534
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3535
                            $type     = $this->tokens[$x]['type'];
×
3536
                            $oldConds = '';
×
3537
                            foreach ($oldConditions as $condition) {
×
3538
                                $oldConds .= Tokens::tokenName($condition).',';
×
3539
                            }
3540

3541
                            $oldConds = rtrim($oldConds, ',');
×
3542

3543
                            $newConds = '';
×
3544
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3545
                                $newConds .= Tokens::tokenName($condition).',';
×
3546
                            }
3547

3548
                            $newConds = rtrim($newConds, ',');
×
3549

3550
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
3551
                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", 3);
×
3552
                        }
3553

3554
                        break;
×
3555
                    }//end if
3556
                }//end foreach
3557
            }//end for
3558
        }//end for
3559

3560
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,707✔
3561
            StatusWriter::write('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3562
        }
3563

3564
    }//end processAdditional()
569✔
3565

3566

3567
    /**
3568
     * Takes a token produced from <code>token_get_all()</code> and produces a
3569
     * more uniform token.
3570
     *
3571
     * @param string|array $token The token to convert.
3572
     *
3573
     * @return array The new token.
3574
     */
3575
    public static function standardiseToken($token)
1✔
3576
    {
3577
        if (isset($token[1]) === false) {
1✔
3578
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
1✔
3579
                return self::$resolveTokenCache[$token[0]];
1✔
3580
            }
3581
        } else {
3582
            $cacheKey = null;
1✔
3583
            if ($token[0] === T_STRING) {
1✔
3584
                $cacheKey = strtolower($token[1]);
1✔
3585
            } else if ($token[0] !== T_CURLY_OPEN) {
1✔
3586
                $cacheKey = $token[0];
1✔
3587
            }
3588

3589
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
1✔
3590
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3591
                $newToken['content'] = $token[1];
×
3592
                return $newToken;
×
3593
            }
3594
        }
3595

3596
        if (isset($token[1]) === false) {
1✔
3597
            return self::resolveSimpleToken($token[0]);
1✔
3598
        }
3599

3600
        if ($token[0] === T_STRING) {
1✔
3601
            switch ($cacheKey) {
1✔
3602
            case 'false':
1✔
3603
                $newToken['type'] = 'T_FALSE';
1✔
3604
                break;
1✔
3605
            case 'true':
1✔
3606
                $newToken['type'] = 'T_TRUE';
1✔
3607
                break;
1✔
3608
            case 'null':
1✔
3609
                $newToken['type'] = 'T_NULL';
1✔
3610
                break;
1✔
3611
            case 'self':
1✔
3612
                $newToken['type'] = 'T_SELF';
1✔
3613
                break;
1✔
3614
            case 'parent':
1✔
3615
                $newToken['type'] = 'T_PARENT';
1✔
3616
                break;
1✔
3617
            default:
3618
                $newToken['type'] = 'T_STRING';
1✔
3619
                break;
1✔
3620
            }
3621

3622
            $newToken['code'] = constant($newToken['type']);
1✔
3623

3624
            self::$resolveTokenCache[$cacheKey] = $newToken;
1✔
3625
        } else if ($token[0] === T_CURLY_OPEN) {
1✔
3626
            $newToken = [
3627
                'code' => T_OPEN_CURLY_BRACKET,
×
3628
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3629
            ];
3630
        } else {
3631
            $newToken = [
3632
                'code' => $token[0],
1✔
3633
                'type' => Tokens::tokenName($token[0]),
1✔
3634
            ];
3635

3636
            self::$resolveTokenCache[$token[0]] = $newToken;
1✔
3637
        }//end if
3638

3639
        $newToken['content'] = $token[1];
1✔
3640
        return $newToken;
1✔
3641

3642
    }//end standardiseToken()
3643

3644

3645
    /**
3646
     * Converts simple tokens into a format that conforms to complex tokens
3647
     * produced by token_get_all().
3648
     *
3649
     * Simple tokens are tokens that are not in array form when produced from
3650
     * token_get_all().
3651
     *
3652
     * @param string $token The simple token to convert.
3653
     *
3654
     * @return array The new token in array format.
3655
     */
3656
    public static function resolveSimpleToken($token)
1✔
3657
    {
3658
        $newToken = [];
1✔
3659

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

3744
        $newToken['code']    = constant($newToken['type']);
1✔
3745
        $newToken['content'] = $token;
1✔
3746

3747
        self::$resolveTokenCache[$token] = $newToken;
1✔
3748
        return $newToken;
1✔
3749

3750
    }//end resolveSimpleToken()
3751

3752

3753
    /**
3754
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3755
     * Handle parenthesis balancing while searching for closing token
3756
     *
3757
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3758
     * @param int             $start        The starting position.
3759
     * @param string|string[] $openerTokens The opening character.
3760
     * @param string          $closerChar   The closing character.
3761
     *
3762
     * @return int|null The position of the closing token, if found. NULL otherwise.
3763
     */
3764
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3765
    {
3766
        $numTokens    = count($tokens);
51✔
3767
        $stack        = [0];
51✔
3768
        $closer       = null;
51✔
3769
        $openerTokens = (array) $openerTokens;
51✔
3770

3771
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3772
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3773
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3774
            ) {
3775
                $stack[] = $x;
51✔
3776
            } else if ($tokens[$x] === $closerChar) {
51✔
3777
                array_pop($stack);
51✔
3778
                if (empty($stack) === true) {
51✔
3779
                    $closer = $x;
51✔
3780
                    break;
51✔
3781
                }
3782
            }
3783
        }
3784

3785
        return $closer;
51✔
3786

3787
    }//end findCloser()
3788

3789

3790
    /**
3791
     * PHP 8 attributes parser for PHP < 8
3792
     * Handles single-line and multiline attributes.
3793
     *
3794
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3795
     * @param int   $stackPtr The current position in token array.
3796
     *
3797
     * @return array|null The array of parsed attribute tokens
3798
     */
3799
    private function parsePhpAttribute(array &$tokens, $stackPtr)
19✔
3800
    {
3801

3802
        $token = $tokens[$stackPtr];
19✔
3803

3804
        $commentBody = substr($token[1], 2);
19✔
3805
        $subTokens   = @token_get_all('<?php '.$commentBody);
19✔
3806

3807
        foreach ($subTokens as $i => $subToken) {
19✔
3808
            if (is_array($subToken) === true
19✔
3809
                && $subToken[0] === T_COMMENT
19✔
3810
                && strpos($subToken[1], '#[') === 0
19✔
3811
            ) {
3812
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
19✔
3813
                if ($reparsed !== null) {
19✔
3814
                    array_splice($subTokens, $i, 1, $reparsed);
19✔
3815
                } else {
3816
                    $subToken[0] = T_ATTRIBUTE;
×
3817
                }
3818
            }
3819
        }
3820

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

3823
        // Go looking for the close bracket.
3824
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3825
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
19✔
3826
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
19✔
3827
                if (is_array($token) === true) {
19✔
3828
                    $commentBody .= $token[1];
19✔
3829
                } else {
3830
                    $commentBody .= $token;
19✔
3831
                }
3832
            }
3833

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

3837
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3838
            if ($bracketCloser !== null) {
19✔
3839
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
19✔
3840
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
19✔
3841
            }
3842
        }
3843

3844
        if ($bracketCloser === null) {
19✔
3845
            return null;
19✔
3846
        }
3847

3848
        return $subTokens;
19✔
3849

3850
    }//end parsePhpAttribute()
3851

3852

3853
    /**
3854
     * Creates a map for the attributes tokens that surround other tokens.
3855
     *
3856
     * @return void
3857
     */
3858
    private function createAttributesNestingMap()
3✔
3859
    {
3860
        $map = [];
3✔
3861
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
3862
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
3863
                && $i === $this->tokens[$i]['attribute_opener']
3✔
3864
            ) {
3865
                if (empty($map) === false) {
3✔
3866
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3867
                }
3868

3869
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
3870
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
3871
                        = $this->tokens[$i]['attribute_closer'];
3✔
3872
                }
3873
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
3874
                && $i === $this->tokens[$i]['attribute_closer']
3✔
3875
            ) {
3876
                array_pop($map);
3✔
3877
                if (empty($map) === false) {
3✔
3878
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3879
                }
3880
            } else {
3881
                if (empty($map) === false) {
3✔
3882
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3883
                }
3884
            }//end if
3885
        }//end for
3886

3887
    }//end createAttributesNestingMap()
1✔
3888

3889

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

© 2026 Coveralls, Inc