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

PHPCSStandards / PHP_CodeSniffer / 14523709455

17 Apr 2025 07:53PM UTC coverage: 77.921% (-0.1%) from 78.02%
14523709455

push

github

web-flow
Merge pull request #1020 from PHPCSStandards/phpcs-4.0/feature/3041-tokenizer-php-namespaced-name-tokenization

Tokenizer/PHP: namespaced names as single token, mirroring PHP 8.0+

203 of 211 new or added lines in 25 files covered. (96.21%)

6 existing lines in 3 files now uncovered.

19389 of 24883 relevant lines covered (77.92%)

85.62 hits per line

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

86.35
/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
     * Regular expression to check if a given identifier name is valid for use in PHP.
21
     *
22
     * @var string
23
     */
24
    private const PHP_LABEL_REGEX = '`^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$`';
25

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

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

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

481
    /**
482
     * Contexts in which keywords should always be tokenized as T_STRING.
483
     *
484
     * @var array
485
     */
486
    protected $tstringContexts = [
487
        T_OBJECT_OPERATOR          => true,
488
        T_NULLSAFE_OBJECT_OPERATOR => true,
489
        T_FUNCTION                 => true,
490
        T_CLASS                    => true,
491
        T_INTERFACE                => true,
492
        T_TRAIT                    => true,
493
        T_ENUM                     => true,
494
        T_ENUM_CASE                => true,
495
        T_EXTENDS                  => true,
496
        T_IMPLEMENTS               => true,
497
        T_ATTRIBUTE                => true,
498
        T_NEW                      => true,
499
        T_CONST                    => true,
500
        T_NS_SEPARATOR             => true,
501
        T_USE                      => true,
502
        T_NAMESPACE                => true,
503
        T_PAAMAYIM_NEKUDOTAYIM     => true,
504
        T_GOTO                     => true,
505
    ];
506

507
    /**
508
     * A cache of different token types, resolved into arrays.
509
     *
510
     * @var array
511
     * @see standardiseToken()
512
     */
513
    private static $resolveTokenCache = [];
514

515

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

536
        $tokens      = @token_get_all($string);
3,330✔
537
        $finalTokens = [];
3,330✔
538

539
        $newStackPtr       = 0;
3,330✔
540
        $numTokens         = count($tokens);
3,330✔
541
        $lastNotEmptyToken = 0;
3,330✔
542

543
        $insideInlineIf         = [];
3,330✔
544
        $insideUseGroup         = false;
3,330✔
545
        $insideConstDeclaration = false;
3,330✔
546

547
        $commentTokenizer = new Comment();
3,330✔
548

549
        for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
3,330✔
550
            // Special case for tokens we have needed to blank out.
551
            if ($tokens[$stackPtr] === null) {
3,330✔
552
                continue;
1,406✔
553
            }
554

555
            $token        = (array) $tokens[$stackPtr];
3,330✔
556
            $tokenIsArray = isset($token[1]);
3,330✔
557

558
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,330✔
559
                if ($tokenIsArray === true) {
×
560
                    $type    = Tokens::tokenName($token[0]);
×
561
                    $content = Common::prepareForOutput($token[1]);
×
562
                } else {
563
                    $newToken = self::resolveSimpleToken($token[0]);
×
564
                    $type     = $newToken['type'];
×
565
                    $content  = Common::prepareForOutput($token[0]);
×
566
                }
567

568
                $statusMessage = 'Process token ';
×
569
                if ($tokenIsArray === true) {
×
570
                    $statusMessage .= "[$stackPtr]";
×
571
                } else {
572
                    $statusMessage .= " $stackPtr ";
×
573
                }
574

575
                $statusMessage .= ": $type => $content";
×
576
                StatusWriter::write($statusMessage, 1, 0);
×
577
            }//end if
578

579
            if ($newStackPtr > 0
3,330✔
580
                && isset(Tokens::$emptyTokens[$finalTokens[($newStackPtr - 1)]['code']]) === false
3,330✔
581
            ) {
582
                $lastNotEmptyToken = ($newStackPtr - 1);
3,330✔
583
            }
584

585
            /*
586
                If we are using \r\n newline characters, the \r and \n are sometimes
587
                split over two tokens. This normally occurs after comments. We need
588
                to merge these two characters together so that our line endings are
589
                consistent for all lines.
590
            */
591

592
            if ($tokenIsArray === true && substr($token[1], -1) === "\r") {
3,330✔
593
                if (isset($tokens[($stackPtr + 1)]) === true
×
594
                    && is_array($tokens[($stackPtr + 1)]) === true
×
595
                    && $tokens[($stackPtr + 1)][1][0] === "\n"
×
596
                ) {
597
                    $token[1] .= "\n";
×
598
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
599
                        if ($isWin === true) {
×
600
                            StatusWriter::write('\n', 0, 0);
×
601
                        } else {
602
                            StatusWriter::write("\033[30;1m\\n\033[0m", 0, 0);
×
603
                        }
604
                    }
605

606
                    if ($tokens[($stackPtr + 1)][1] === "\n") {
×
607
                        // This token's content has been merged into the previous,
608
                        // so we can skip it.
609
                        $tokens[($stackPtr + 1)] = '';
×
610
                    } else {
611
                        $tokens[($stackPtr + 1)][1] = substr($tokens[($stackPtr + 1)][1], 1);
×
612
                    }
613
                }
614
            }//end if
615

616
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,330✔
617
                StatusWriter::writeNewline();
×
618
            }
619

620
            /*
621
                Tokenize context sensitive keyword as string when it should be string.
622
            */
623

624
            if ($tokenIsArray === true
3,330✔
625
                && isset(Tokens::$contextSensitiveKeywords[$token[0]]) === true
3,330✔
626
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
3,230✔
627
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
3,230✔
628
                || $insideConstDeclaration === true)
3,330✔
629
            ) {
630
                if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,597✔
631
                    $preserveKeyword = false;
2,597✔
632

633
                    // `new class`, and `new static` should be preserved.
634
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,597✔
635
                        && ($token[0] === T_CLASS
2,273✔
636
                        || $token[0] === T_STATIC)
2,597✔
637
                    ) {
638
                        $preserveKeyword = true;
1,518✔
639
                    }
640

641
                    // `new readonly class` should be preserved.
642
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,597✔
643
                        && strtolower($token[1]) === 'readonly'
2,597✔
644
                    ) {
645
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
338✔
646
                            if (is_array($tokens[$i]) === false
338✔
647
                                || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
338✔
648
                            ) {
649
                                break;
338✔
650
                            }
651
                        }
652

653
                        if (is_array($tokens[$i]) === true && $tokens[$i][0] === T_CLASS) {
338✔
654
                            $preserveKeyword = true;
338✔
655
                        }
656
                    }
657

658
                    // `new class extends` `new class implements` should be preserved
659
                    if (($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS)
2,597✔
660
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CLASS
2,597✔
661
                    ) {
662
                        $preserveKeyword = true;
507✔
663
                    }
664

665
                    // `namespace\` should be preserved
666
                    if ($token[0] === T_NAMESPACE) {
2,597✔
667
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
827✔
668
                            if (is_array($tokens[$i]) === false) {
827✔
669
                                break;
717✔
670
                            }
671

672
                            if (isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true) {
617✔
673
                                continue;
507✔
674
                            }
675

676
                            if ($tokens[$i][0] === T_NS_SEPARATOR) {
110✔
677
                                $preserveKeyword = true;
110✔
678
                            }
679

680
                            break;
110✔
681
                        }
682
                    }
683
                }//end if
684

685
                // Types in typed constants should not be touched, but the constant name should be.
686
                if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,597✔
687
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,597✔
688
                    || $insideConstDeclaration === true
2,597✔
689
                ) {
690
                    $preserveKeyword = true;
2,246✔
691

692
                    // Find the next non-empty token.
693
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,246✔
694
                        if (is_array($tokens[$i]) === true
2,246✔
695
                            && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,246✔
696
                        ) {
697
                            continue;
1,607✔
698
                        }
699

700
                        break;
2,246✔
701
                    }
702

703
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
2,246✔
704
                        $preserveKeyword        = false;
797✔
705
                        $insideConstDeclaration = false;
797✔
706
                    }
707
                }//end if
708

709
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,597✔
710
                    $preserveKeyword = true;
624✔
711

712
                    for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
624✔
713
                        if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
624✔
714
                            continue;
561✔
715
                        }
716

717
                        if ($finalTokens[$i]['code'] === T_FUNCTION) {
624✔
718
                            $preserveKeyword = false;
561✔
719
                        }
720

721
                        break;
624✔
722
                    }
723
                }
724

725
                if ($preserveKeyword === false) {
2,597✔
726
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,228✔
727
                        $type = Tokens::tokenName($token[0]);
×
728
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
729
                    }
730

731
                    $finalTokens[$newStackPtr] = [
1,228✔
732
                        'code'    => T_STRING,
1,228✔
733
                        'type'    => 'T_STRING',
1,228✔
734
                        'content' => $token[1],
1,228✔
735
                    ];
848✔
736

737
                    $newStackPtr++;
1,228✔
738
                    continue;
1,228✔
739
                }
740
            }//end if
741

742
            /*
743
                Mark the start of a constant declaration to allow for handling keyword to T_STRING
744
                convertion for constant names using reserved keywords.
745
            */
746

747
            if ($tokenIsArray === true && $token[0] === T_CONST) {
3,330✔
748
                $insideConstDeclaration = true;
2,436✔
749
            }
750

751
            /*
752
                Close an open "inside constant declaration" marker when no keyword conversion was needed.
753
            */
754

755
            if ($insideConstDeclaration === true
3,330✔
756
                && $tokenIsArray === false
3,330✔
757
                && ($token[0] === '=' || $token[0] === ';')
3,330✔
758
            ) {
759
                $insideConstDeclaration = false;
949✔
760
            }
761

762
            /*
763
                Special case for `static` used as a function name, i.e. `static()`.
764

765
                Note: this may incorrectly change the static keyword directly before a DNF property type.
766
                If so, this will be caught and corrected for in the additional processing.
767
            */
768

769
            if ($tokenIsArray === true
3,330✔
770
                && $token[0] === T_STATIC
3,330✔
771
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
3,330✔
772
            ) {
773
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,049✔
774
                    if (is_array($tokens[$i]) === true
2,049✔
775
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,049✔
776
                    ) {
777
                        continue;
1,410✔
778
                    }
779

780
                    if ($tokens[$i][0] === '(') {
2,049✔
781
                        $finalTokens[$newStackPtr] = [
507✔
782
                            'code'    => T_STRING,
507✔
783
                            'type'    => 'T_STRING',
507✔
784
                            'content' => $token[1],
507✔
785
                        ];
338✔
786

787
                        $newStackPtr++;
507✔
788
                        continue 2;
507✔
789
                    }
790

791
                    break;
2,049✔
792
                }
793
            }//end if
794

795
            /*
796
                Prior to PHP 7.4, PHP didn't support stand-alone PHP open tags at the end of a file
797
                (without a new line), so we need to make sure that the tokenization in PHPCS is consistent
798
                cross-version PHP by retokenizing to T_OPEN_TAG.
799
            */
800

801
            if (PHP_VERSION_ID < 70400
3,330✔
802
                && $tokenIsArray === true
3,330✔
803
                // PHP < 7.4 with short open tags off.
804
                && (($stackPtr === ($numTokens - 1)
3,330✔
805
                && $token[0] === T_INLINE_HTML
3,330✔
806
                && stripos($token[1], '<?php') === 0)
2,243✔
807
                // PHP < 7.4 with short open tags on.
2,222✔
808
                || ($stackPtr === ($numTokens - 2)
3,330✔
809
                && $token[0] === T_OPEN_TAG
3,330✔
810
                && $token[1] === '<?'
3,330✔
811
                && is_array($tokens[($stackPtr + 1)]) === true
3,330✔
812
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,330✔
813
                && strtolower($tokens[($stackPtr + 1)][1]) === 'php'))
3,330✔
814
            ) {
815
                if ($token[0] === T_INLINE_HTML) {
2✔
816
                    $finalTokens[$newStackPtr] = [
2✔
817
                        'code'    => T_OPEN_TAG,
2✔
818
                        'type'    => 'T_OPEN_TAG',
2✔
819
                        'content' => $token[1],
2✔
820
                    ];
821
                } else {
822
                    $finalTokens[$newStackPtr] = [
×
823
                        'code'    => T_OPEN_TAG,
×
824
                        'type'    => 'T_OPEN_TAG',
×
825
                        'content' => $token[1].$tokens[($stackPtr + 1)][1],
×
826
                    ];
827

828
                    $stackPtr++;
×
829
                }
830

831
                $newStackPtr++;
2✔
832
                continue;
2✔
833
            }//end if
834

835
            /*
836
                Split whitespace off from long PHP open tag tokens and potentially join the whitespace
837
                with a subsequent whitespace token.
838
            */
839

840
            if ($tokenIsArray === true
3,330✔
841
                && $token[0] === T_OPEN_TAG
3,330✔
842
                && stripos($token[1], '<?php') === 0
3,330✔
843
            ) {
844
                $openTagAndWhiteSpace = str_split($token[1], 5);
3,330✔
845

846
                $finalTokens[$newStackPtr] = [
3,330✔
847
                    'code'    => T_OPEN_TAG,
3,330✔
848
                    'type'    => 'T_OPEN_TAG',
3,330✔
849
                    'content' => $openTagAndWhiteSpace[0],
3,330✔
850
                ];
2,222✔
851
                $newStackPtr++;
3,330✔
852

853
                if (isset($openTagAndWhiteSpace[1]) === true) {
3,330✔
854
                    // The original open tag token included whitespace.
855
                    // Check whether a new whitespace token needs to be inserted or if the
856
                    // whitespace needs to be joined with a pre-existing whitespace
857
                    // token on the same line as the open tag.
858
                    if (isset($tokens[($stackPtr + 1)]) === true
3,330✔
859
                        && $openTagAndWhiteSpace[1] === ' '
3,330✔
860
                        && is_array($tokens[($stackPtr + 1)]) === true
3,330✔
861
                        && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,330✔
862
                    ) {
863
                        // Adjusting the original token stack as the "new line may be split over two tokens"
864
                        // check should still be run on this token.
865
                        $tokens[($stackPtr + 1)][1] = $openTagAndWhiteSpace[1].$tokens[($stackPtr + 1)][1];
63✔
866

867
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
63✔
868
                            StatusWriter::write("* removed whitespace from T_OPEN_TAG token $stackPtr and merged it with the next token T_WHITESPACE", 2);
44✔
869
                        }
870
                    } else {
871
                        $finalTokens[$newStackPtr] = [
3,330✔
872
                            'code'    => T_WHITESPACE,
3,330✔
873
                            'type'    => 'T_WHITESPACE',
3,330✔
874
                            'content' => $openTagAndWhiteSpace[1],
3,330✔
875
                        ];
2,222✔
876

877
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,330✔
878
                            StatusWriter::write("* T_OPEN_TAG token $stackPtr split into T_OPEN_TAG (without whitespace) and new T_WHITESPACE token", 2);
×
879
                        }
880

881
                        $newStackPtr++;
3,330✔
882
                    }//end if
883
                }//end if
884

885
                continue;
3,330✔
886
            }//end if
887

888
            /*
889
                Parse doc blocks into something that can be easily iterated over.
890
            */
891

892
            if ($tokenIsArray === true
3,330✔
893
                && ($token[0] === T_DOC_COMMENT
3,330✔
894
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
3,330✔
895
            ) {
896
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
93✔
897
                $finalTokens  += $commentTokens;
93✔
898
                $newStackPtr  += count($commentTokens);
93✔
899
                continue;
93✔
900
            }
901

902
            /*
903
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
904
            */
905

906
            if (PHP_VERSION_ID >= 80000
3,330✔
907
                && $tokenIsArray === true
3,330✔
908
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
3,330✔
909
                && isset($tokens[($stackPtr + 1)]) === true
3,330✔
910
                && is_array($tokens[($stackPtr + 1)]) === true
3,330✔
911
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,330✔
912
            ) {
913
                $nextToken = $tokens[($stackPtr + 1)];
1,722✔
914

915
                // If the next token is a single new line, merge it into the comment token
916
                // and set to it up to be skipped.
917
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
1,722✔
918
                    $token[1] .= $nextToken[1];
1,406✔
919
                    $tokens[($stackPtr + 1)] = null;
1,406✔
920

921
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,406✔
922
                        StatusWriter::write("* merged newline after comment into comment token $stackPtr", 2);
703✔
923
                    }
924
                } else {
925
                    // This may be a whitespace token consisting of multiple new lines.
926
                    if (strpos($nextToken[1], "\r\n") === 0) {
1,382✔
927
                        $token[1] .= "\r\n";
30✔
928
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
30✔
929
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
1,352✔
930
                        $token[1] .= "\n\r";
×
931
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
932
                    } else if (strpos($nextToken[1], "\n") === 0) {
1,352✔
933
                        $token[1] .= "\n";
1,352✔
934
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
1,352✔
935
                    }
936

937
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,382✔
938
                        StatusWriter::write("* stripped first newline after comment and added it to comment token $stackPtr", 2);
×
939
                    }
940
                }//end if
941
            }//end if
942

943
            /*
944
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
945
                T_LNUMBER and T_STRING token values into a single token value, and
946
                then ignore the T_STRING token.
947
            */
948

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

966
                if (isset($matches[2]) === true && $matches[2] !== '') {
37✔
967
                    $type = 'T_LNUMBER';
10✔
968
                    if ($matches[2][0] === '_') {
10✔
969
                        $type = 'T_STRING';
10✔
970
                    }
971

972
                    $finalTokens[$newStackPtr] = [
10✔
973
                        'code'    => constant($type),
10✔
974
                        'type'    => $type,
10✔
975
                        'content' => $matches[2],
10✔
976
                    ];
977
                    $newStackPtr++;
10✔
978
                }
979

980
                $stackPtr++;
37✔
981
                continue;
37✔
982
            }//end if
983

984
            /*
985
                PHP 8.1 introduced two dedicated tokens for the & character.
986
                Retokenizing both of these to T_BITWISE_AND, which is the
987
                token PHPCS already tokenized them as.
988
            */
989

990
            if ($tokenIsArray === true
3,330✔
991
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
3,330✔
992
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
3,330✔
993
            ) {
994
                $finalTokens[$newStackPtr] = [
858✔
995
                    'code'    => T_BITWISE_AND,
858✔
996
                    'type'    => 'T_BITWISE_AND',
858✔
997
                    'content' => $token[1],
858✔
998
                ];
858✔
999
                $newStackPtr++;
858✔
1000
                continue;
858✔
1001
            }
1002

1003
            /*
1004
                If this is a double quoted string, PHP will tokenize the whole
1005
                thing which causes problems with the scope map when braces are
1006
                within the string. So we need to merge the tokens together to
1007
                provide a single string.
1008
            */
1009

1010
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
3,330✔
1011
                // Binary casts need a special token.
1012
                if ($token[0] === 'b"') {
150✔
1013
                    $finalTokens[$newStackPtr] = [
×
1014
                        'code'    => T_BINARY_CAST,
×
1015
                        'type'    => 'T_BINARY_CAST',
×
1016
                        'content' => 'b',
×
1017
                    ];
1018
                    $newStackPtr++;
×
1019
                }
1020

1021
                $tokenContent = '"';
150✔
1022
                $nestedVars   = [];
150✔
1023
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
150✔
1024
                    $subToken        = (array) $tokens[$i];
150✔
1025
                    $subTokenIsArray = isset($subToken[1]);
150✔
1026

1027
                    if ($subTokenIsArray === true) {
150✔
1028
                        $tokenContent .= $subToken[1];
150✔
1029
                        if (($subToken[1] === '{'
150✔
1030
                            || $subToken[1] === '${')
150✔
1031
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
150✔
1032
                        ) {
1033
                            $nestedVars[] = $i;
120✔
1034
                        }
1035
                    } else {
1036
                        $tokenContent .= $subToken[0];
150✔
1037
                        if ($subToken[0] === '}') {
150✔
1038
                            array_pop($nestedVars);
60✔
1039
                        }
1040
                    }
1041

1042
                    if ($subTokenIsArray === false
150✔
1043
                        && $subToken[0] === '"'
150✔
1044
                        && empty($nestedVars) === true
150✔
1045
                    ) {
1046
                        // We found the other end of the double quoted string.
1047
                        break;
150✔
1048
                    }
1049
                }//end for
1050

1051
                $stackPtr = $i;
150✔
1052

1053
                // Convert each line within the double quoted string to a
1054
                // new token, so it conforms with other multiple line tokens.
1055
                $tokenLines = explode($this->eolChar, $tokenContent);
150✔
1056
                $numLines   = count($tokenLines);
150✔
1057
                $newToken   = [];
150✔
1058

1059
                for ($j = 0; $j < $numLines; $j++) {
150✔
1060
                    $newToken['content'] = $tokenLines[$j];
150✔
1061
                    if ($j === ($numLines - 1)) {
150✔
1062
                        if ($tokenLines[$j] === '') {
150✔
1063
                            break;
120✔
1064
                        }
1065
                    } else {
1066
                        $newToken['content'] .= $this->eolChar;
60✔
1067
                    }
1068

1069
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
150✔
1070
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
150✔
1071
                    $finalTokens[$newStackPtr] = $newToken;
150✔
1072
                    $newStackPtr++;
150✔
1073
                }
1074

1075
                // Continue, as we're done with this token.
1076
                continue;
150✔
1077
            }//end if
1078

1079
            /*
1080
                Detect binary casting and assign the casts their own token.
1081
            */
1082

1083
            if ($tokenIsArray === true
3,330✔
1084
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
3,330✔
1085
                && (substr($token[1], 0, 2) === 'b"'
3,152✔
1086
                || substr($token[1], 0, 2) === "b'")
3,330✔
1087
            ) {
1088
                $finalTokens[$newStackPtr] = [
×
1089
                    'code'    => T_BINARY_CAST,
×
1090
                    'type'    => 'T_BINARY_CAST',
×
1091
                    'content' => 'b',
×
1092
                ];
1093
                $newStackPtr++;
×
1094
                $token[1] = substr($token[1], 1);
×
1095
            }
1096

1097
            if ($tokenIsArray === true
3,330✔
1098
                && $token[0] === T_STRING_CAST
3,330✔
1099
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
3,330✔
1100
            ) {
1101
                $finalTokens[$newStackPtr] = [
×
1102
                    'code'    => T_BINARY_CAST,
×
1103
                    'type'    => 'T_BINARY_CAST',
×
1104
                    'content' => $token[1],
×
1105
                ];
1106
                $newStackPtr++;
×
1107
                continue;
×
1108
            }
1109

1110
            /*
1111
                If this is a heredoc, PHP will tokenize the whole
1112
                thing which causes problems when heredocs don't
1113
                contain real PHP code, which is almost never.
1114
                We want to leave the start and end heredoc tokens
1115
                alone though.
1116
            */
1117

1118
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
3,330✔
1119
                // Add the start heredoc token to the final array.
1120
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
135✔
1121

1122
                // Check if this is actually a nowdoc and use a different token
1123
                // to help the sniffs.
1124
                $nowdoc = false;
135✔
1125
                if (strpos($token[1], "'") !== false) {
135✔
1126
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
18✔
1127
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
18✔
1128
                    $nowdoc = true;
18✔
1129
                }
1130

1131
                $tokenContent = '';
135✔
1132
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
135✔
1133
                    $subTokenIsArray = is_array($tokens[$i]);
135✔
1134
                    if ($subTokenIsArray === true
135✔
1135
                        && $tokens[$i][0] === T_END_HEREDOC
135✔
1136
                    ) {
1137
                        // We found the other end of the heredoc.
1138
                        break;
132✔
1139
                    }
1140

1141
                    if ($subTokenIsArray === true) {
135✔
1142
                        $tokenContent .= $tokens[$i][1];
135✔
1143
                    } else {
1144
                        $tokenContent .= $tokens[$i];
114✔
1145
                    }
1146
                }
1147

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

1158
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
3✔
1159
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
3✔
1160
                    $newStackPtr++;
3✔
1161
                    continue;
3✔
1162
                }
1163

1164
                $stackPtr = $i;
132✔
1165
                $newStackPtr++;
132✔
1166

1167
                // Convert each line within the heredoc to a
1168
                // new token, so it conforms with other multiple line tokens.
1169
                $tokenLines = explode($this->eolChar, $tokenContent);
132✔
1170
                $numLines   = count($tokenLines);
132✔
1171
                $newToken   = [];
132✔
1172

1173
                for ($j = 0; $j < $numLines; $j++) {
132✔
1174
                    $newToken['content'] = $tokenLines[$j];
132✔
1175
                    if ($j === ($numLines - 1)) {
132✔
1176
                        if ($tokenLines[$j] === '') {
132✔
1177
                            break;
132✔
1178
                        }
1179
                    } else {
1180
                        $newToken['content'] .= $this->eolChar;
132✔
1181
                    }
1182

1183
                    if ($nowdoc === true) {
132✔
1184
                        $newToken['code'] = T_NOWDOC;
18✔
1185
                        $newToken['type'] = 'T_NOWDOC';
18✔
1186
                    } else {
1187
                        $newToken['code'] = T_HEREDOC;
132✔
1188
                        $newToken['type'] = 'T_HEREDOC';
132✔
1189
                    }
1190

1191
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1192
                    $newStackPtr++;
132✔
1193
                }//end for
1194

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

1198
                if ($nowdoc === true) {
132✔
1199
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
18✔
1200
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
18✔
1201
                }
1202

1203
                $newStackPtr++;
132✔
1204

1205
                // Continue, as we're done with this token.
1206
                continue;
132✔
1207
            }//end if
1208

1209
            /*
1210
                Enum keyword for PHP < 8.1
1211
            */
1212

1213
            if ($tokenIsArray === true
3,330✔
1214
                && $token[0] === T_STRING
3,330✔
1215
                && strtolower($token[1]) === 'enum'
3,330✔
1216
            ) {
1217
                // Get the next non-empty token.
1218
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,578✔
1219
                    if (is_array($tokens[$i]) === false
1,578✔
1220
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,578✔
1221
                    ) {
1222
                        break;
1,578✔
1223
                    }
1224
                }
1225

1226
                if (isset($tokens[$i]) === true
1,578✔
1227
                    && is_array($tokens[$i]) === true
1,578✔
1228
                    && $tokens[$i][0] === T_STRING
1,578✔
1229
                ) {
1230
                    // Modify $tokens directly so we can use it later when converting enum "case".
1231
                    $tokens[$stackPtr][0] = T_ENUM;
327✔
1232

1233
                    $newToken            = [];
327✔
1234
                    $newToken['code']    = T_ENUM;
327✔
1235
                    $newToken['type']    = 'T_ENUM';
327✔
1236
                    $newToken['content'] = $token[1];
327✔
1237
                    $finalTokens[$newStackPtr] = $newToken;
327✔
1238

1239
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
327✔
1240
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_ENUM", 2);
×
1241
                    }
1242

1243
                    $newStackPtr++;
327✔
1244
                    continue;
327✔
1245
                }
1246
            }//end if
1247

1248
            /*
1249
                Convert enum "case" to T_ENUM_CASE
1250
            */
1251

1252
            if ($tokenIsArray === true
3,330✔
1253
                && $token[0] === T_CASE
3,330✔
1254
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,330✔
1255
            ) {
1256
                $isEnumCase = false;
1,596✔
1257
                $scope      = 1;
1,596✔
1258

1259
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,596✔
1260
                    if ($tokens[$i] === '}') {
1,596✔
1261
                        $scope++;
1,506✔
1262
                        continue;
1,506✔
1263
                    }
1264

1265
                    if ($tokens[$i] === '{') {
1,596✔
1266
                        $scope--;
1,596✔
1267
                        continue;
1,596✔
1268
                    }
1269

1270
                    if (is_array($tokens[$i]) === false) {
1,596✔
1271
                        continue;
1,596✔
1272
                    }
1273

1274
                    if ($scope !== 0) {
1,596✔
1275
                        continue;
1,596✔
1276
                    }
1277

1278
                    if ($tokens[$i][0] === T_SWITCH) {
1,089✔
1279
                        break;
1,023✔
1280
                    }
1281

1282
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,089✔
1283
                        $isEnumCase = true;
129✔
1284
                        break;
129✔
1285
                    }
1286
                }//end for
1287

1288
                if ($isEnumCase === true) {
1,596✔
1289
                    // Modify $tokens directly so we can use it as optimisation for other enum "case".
1290
                    $tokens[$stackPtr][0] = T_ENUM_CASE;
129✔
1291

1292
                    $newToken            = [];
129✔
1293
                    $newToken['code']    = T_ENUM_CASE;
129✔
1294
                    $newToken['type']    = 'T_ENUM_CASE';
129✔
1295
                    $newToken['content'] = $token[1];
129✔
1296
                    $finalTokens[$newStackPtr] = $newToken;
129✔
1297

1298
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
129✔
1299
                        StatusWriter::write("* token $stackPtr changed from T_CASE to T_ENUM_CASE", 2);
×
1300
                    }
1301

1302
                    $newStackPtr++;
129✔
1303
                    continue;
129✔
1304
                }
1305
            }//end if
1306

1307
            /*
1308
                Before PHP 8.0, namespaced names were not tokenized as a single token.
1309

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

1316
            if (PHP_VERSION_ID < 80000
3,330✔
1317
                && $tokenIsArray === true
3,330✔
1318
                && ($token[0] === T_STRING
3,330✔
1319
                || $token[0] === T_NAMESPACE
3,330✔
1320
                || ($token[0] === T_NS_SEPARATOR
3,330✔
1321
                && isset($tokens[($stackPtr + 1)]) === true
3,330✔
1322
                && is_array($tokens[($stackPtr + 1)]) === true
3,330✔
1323
                && isset(Tokens::$emptyTokens[$tokens[($stackPtr + 1)][0]]) === false
3,330✔
1324
                && preg_match(self::PHP_LABEL_REGEX, $tokens[($stackPtr + 1)][1]) === 1))
3,330✔
1325
            ) {
1326
                $nameStart = $stackPtr;
976✔
1327
                $i         = $stackPtr;
976✔
1328
                $newToken  = [];
976✔
1329
                $newToken['content'] = $token[1];
976✔
1330

1331
                switch ($token[0]) {
976✔
1332
                case T_STRING:
976✔
1333
                    $newToken['code'] = T_NAME_QUALIFIED;
976✔
1334
                    $newToken['type'] = 'T_NAME_QUALIFIED';
976✔
1335
                    break;
976✔
1336
                case T_NAMESPACE:
608✔
1337
                    $newToken['code'] = T_NAME_RELATIVE;
591✔
1338
                    $newToken['type'] = 'T_NAME_RELATIVE';
591✔
1339
                    break;
591✔
1340
                case T_NS_SEPARATOR:
356✔
1341
                    $newToken['code'] = T_NAME_FULLY_QUALIFIED;
356✔
1342
                    $newToken['type'] = 'T_NAME_FULLY_QUALIFIED';
356✔
1343

1344
                    if (is_array($tokens[($i - 1)]) === true
356✔
1345
                        && isset(Tokens::$emptyTokens[$tokens[($i - 1)][0]]) === false
356✔
1346
                        && preg_match(self::PHP_LABEL_REGEX, $tokens[($i - 1)][1]) === 1
356✔
1347
                    ) {
1348
                        // The namespaced name starts with a reserved keyword. Move one token back.
1349
                        $newToken['code']    = T_NAME_QUALIFIED;
47✔
1350
                        $newToken['type']    = 'T_NAME_QUALIFIED';
47✔
1351
                        $newToken['content'] = $tokens[($i - 1)][1];
47✔
1352
                        --$nameStart;
47✔
1353
                        --$i;
47✔
1354
                        break;
47✔
1355
                    }
1356

1357
                    ++$i;
356✔
1358
                    $newToken['content'] .= $tokens[$i][1];
356✔
1359
                    break;
356✔
1360
                }//end switch
1361

1362
                while (isset($tokens[($i + 1)], $tokens[($i + 2)]) === true
976✔
1363
                    && is_array($tokens[($i + 1)]) === true && $tokens[($i + 1)][0] === T_NS_SEPARATOR
976✔
1364
                    && is_array($tokens[($i + 2)]) === true
976✔
1365
                    && isset(Tokens::$emptyTokens[$tokens[($i + 2)][0]]) === false
976✔
1366
                    && preg_match(self::PHP_LABEL_REGEX, $tokens[($i + 2)][1]) === 1
976✔
1367
                ) {
1368
                    $newToken['content'] .= $tokens[($i + 1)][1].$tokens[($i + 2)][1];
469✔
1369
                    $i = ($i + 2);
469✔
1370
                }
1371

1372
                if ($i !== $nameStart) {
976✔
1373
                    if ($nameStart !== $stackPtr) {
469✔
1374
                        // This must be a qualified name starting with a reserved keyword.
1375
                        // We need to overwrite the previously set final token.
1376
                        --$newStackPtr;
47✔
1377
                    }
1378

1379
                    $finalTokens[$newStackPtr] = $newToken;
469✔
1380
                    $newStackPtr++;
469✔
1381
                    $stackPtr = $i;
469✔
1382

1383
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
469✔
NEW
1384
                        $type    = $newToken['type'];
×
NEW
1385
                        $content = $newToken['content'];
×
NEW
1386
                        StatusWriter::write("* token $nameStart to $i ($content) retokenized to $type", 2);
×
1387
                    }
1388

1389
                    continue;
469✔
1390
                }//end if
1391
            }//end if
1392

1393
            /*
1394
                PHP 8.0 Attributes
1395
            */
1396

1397
            if (PHP_VERSION_ID < 80000
3,330✔
1398
                && $token[0] === T_COMMENT
3,330✔
1399
                && strpos($token[1], '#[') === 0
3,330✔
1400
            ) {
1401
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
17✔
1402
                if ($subTokens !== null) {
17✔
1403
                    array_splice($tokens, $stackPtr, 1, $subTokens);
17✔
1404
                    $numTokens = count($tokens);
17✔
1405

1406
                    $tokenIsArray = true;
17✔
1407
                    $token        = $tokens[$stackPtr];
17✔
1408
                } else {
1409
                    $token[0] = T_ATTRIBUTE;
17✔
1410
                }
1411
            }
1412

1413
            if ($tokenIsArray === true
3,330✔
1414
                && $token[0] === T_ATTRIBUTE
3,330✔
1415
            ) {
1416
                // Go looking for the close bracket.
1417
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1418

1419
                $newToken            = [];
51✔
1420
                $newToken['code']    = T_ATTRIBUTE;
51✔
1421
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1422
                $newToken['content'] = '#[';
51✔
1423
                $finalTokens[$newStackPtr] = $newToken;
51✔
1424

1425
                $tokens[$bracketCloser]    = [];
51✔
1426
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1427
                $tokens[$bracketCloser][1] = ']';
51✔
1428

1429
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1430
                    StatusWriter::write("* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END", 2);
×
1431
                }
1432

1433
                $newStackPtr++;
51✔
1434
                continue;
51✔
1435
            }//end if
1436

1437
            /*
1438
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1439
                token and ensures that the colon after it is always T_COLON.
1440
            */
1441

1442
            if ($tokenIsArray === true
3,330✔
1443
                && ($token[0] === T_STRING
3,330✔
1444
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
3,330✔
1445
            ) {
1446
                // Get the next non-empty token.
1447
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
3,144✔
1448
                    if (is_array($tokens[$i]) === false
3,144✔
1449
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
3,144✔
1450
                    ) {
1451
                        break;
3,144✔
1452
                    }
1453
                }
1454

1455
                if (isset($tokens[$i]) === true
3,144✔
1456
                    && is_array($tokens[$i]) === false
3,144✔
1457
                    && $tokens[$i] === ':'
3,144✔
1458
                ) {
1459
                    // Get the previous non-empty token.
1460
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,668✔
1461
                        if (is_array($tokens[$j]) === false
1,668✔
1462
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
1,668✔
1463
                        ) {
1464
                            break;
1,668✔
1465
                        }
1466
                    }
1467

1468
                    if (is_array($tokens[$j]) === false
1,668✔
1469
                        && ($tokens[$j] === '('
1,618✔
1470
                        || $tokens[$j] === ',')
1,668✔
1471
                    ) {
1472
                        $newToken            = [];
690✔
1473
                        $newToken['code']    = T_PARAM_NAME;
690✔
1474
                        $newToken['type']    = 'T_PARAM_NAME';
690✔
1475
                        $newToken['content'] = $token[1];
690✔
1476
                        $finalTokens[$newStackPtr] = $newToken;
690✔
1477

1478
                        $newStackPtr++;
690✔
1479

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

1484
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
690✔
1485
                            $type = Tokens::tokenName($token[0]);
×
1486
                            StatusWriter::write("* token $stackPtr changed from $type to T_PARAM_NAME", 2);
×
1487
                        }
1488

1489
                        continue;
690✔
1490
                    }
1491
                }//end if
1492
            }//end if
1493

1494
            /*
1495
                "readonly" keyword for PHP < 8.1
1496
            */
1497

1498
            if ($tokenIsArray === true
3,330✔
1499
                && strtolower($token[1]) === 'readonly'
3,330✔
1500
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,623✔
1501
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
3,330✔
1502
            ) {
1503
                // Get the next non-whitespace token.
1504
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
993✔
1505
                    if (is_array($tokens[$i]) === false
993✔
1506
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
993✔
1507
                    ) {
1508
                        break;
993✔
1509
                    }
1510
                }
1511

1512
                $isReadonlyKeyword = false;
993✔
1513

1514
                if (isset($tokens[$i]) === false
993✔
1515
                    || $tokens[$i] !== '('
993✔
1516
                ) {
1517
                    $isReadonlyKeyword = true;
993✔
1518
                } else if ($tokens[$i] === '(') {
×
1519
                    /*
1520
                     * Skip over tokens which can be used in type declarations.
1521
                     * At this point, the only token types which need to be taken into consideration
1522
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1523
                     * and the union/intersection/dnf parentheses.
1524
                     */
1525

1526
                    $foundDNFParens = 1;
×
1527
                    $foundDNFPipe   = 0;
×
1528

1529
                    for (++$i; $i < $numTokens; $i++) {
×
1530
                        if (is_array($tokens[$i]) === true) {
×
1531
                            $tokenType = $tokens[$i][0];
×
1532
                        } else {
1533
                            $tokenType = $tokens[$i];
×
1534
                        }
1535

1536
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1537
                            continue;
×
1538
                        }
1539

1540
                        if ($tokenType === '|') {
×
1541
                            ++$foundDNFPipe;
×
1542
                            continue;
×
1543
                        }
1544

1545
                        if ($tokenType === ')') {
×
1546
                            ++$foundDNFParens;
×
1547
                            continue;
×
1548
                        }
1549

1550
                        if ($tokenType === '(') {
×
1551
                            ++$foundDNFParens;
×
1552
                            continue;
×
1553
                        }
1554

1555
                        if ($tokenType === T_STRING
×
1556
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1557
                            || $tokenType === T_NAME_RELATIVE
×
1558
                            || $tokenType === T_NAME_QUALIFIED
×
1559
                            || $tokenType === T_ARRAY
×
1560
                            || $tokenType === T_NAMESPACE
×
1561
                            || $tokenType === T_NS_SEPARATOR
×
1562
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1563
                            || $tokenType === '&' // PHP < 8.0.
×
1564
                        ) {
1565
                            continue;
×
1566
                        }
1567

1568
                        // Reached the next token after.
1569
                        if (($foundDNFParens % 2) === 0
×
1570
                            && $foundDNFPipe >= 1
×
1571
                            && ($tokenType === T_VARIABLE
×
1572
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1573
                        ) {
1574
                            $isReadonlyKeyword = true;
×
1575
                        }
1576

1577
                        break;
×
1578
                    }//end for
1579
                }//end if
1580

1581
                if ($isReadonlyKeyword === true) {
993✔
1582
                    $finalTokens[$newStackPtr] = [
993✔
1583
                        'code'    => T_READONLY,
993✔
1584
                        'type'    => 'T_READONLY',
993✔
1585
                        'content' => $token[1],
993✔
1586
                    ];
662✔
1587
                    $newStackPtr++;
993✔
1588

1589
                    // Also modify the original token stack so that
1590
                    // future checks (like looking for T_NULLABLE) can
1591
                    // detect the T_READONLY token more easily.
1592
                    $tokens[$stackPtr][0] = T_READONLY;
993✔
1593
                    $token[0] = T_READONLY;
993✔
1594

1595
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
993✔
1596
                        StatusWriter::write("* token $stackPtr changed from $type to T_READONLY", 2);
662✔
1597
                    }
1598
                } else {
1599
                    $finalTokens[$newStackPtr] = [
×
1600
                        'code'    => T_STRING,
×
1601
                        'type'    => 'T_STRING',
×
1602
                        'content' => $token[1],
×
1603
                    ];
1604
                    $newStackPtr++;
×
1605

1606
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1607
                        StatusWriter::write("* token $stackPtr changed from $type to T_STRING", 2);
×
1608
                    }
1609
                }//end if
1610

1611
                continue;
993✔
1612
            }//end if
1613

1614
            /*
1615
                Deal with "yield from" in various PHP versions.
1616
            */
1617

1618
            if (PHP_VERSION_ID < 80300
3,330✔
1619
                && $tokenIsArray === true
3,330✔
1620
                && $token[0] === T_STRING
3,330✔
1621
                && strtolower($token[1]) === 'from'
3,330✔
1622
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
3,330✔
1623
            ) {
1624
                /*
1625
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1626
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1627
                    We want to keep the tokenization of the tokens between, but need to change the
1628
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1629
                */
1630

1631
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
36✔
1632
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
36✔
1633

1634
                $finalTokens[$newStackPtr] = [
36✔
1635
                    'code'    => T_YIELD_FROM,
36✔
1636
                    'type'    => 'T_YIELD_FROM',
36✔
1637
                    'content' => $token[1],
36✔
1638
                ];
18✔
1639
                $newStackPtr++;
36✔
1640

1641
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
36✔
1642
                    StatusWriter::write("* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD", 2);
×
1643
                    StatusWriter::write("* token $stackPtr changed into T_YIELD_FROM; was: T_STRING", 2);
×
1644
                }
1645

1646
                continue;
36✔
1647
            } else if ($tokenIsArray === true
3,330✔
1648
                && $token[0] === T_YIELD_FROM
3,330✔
1649
                && strpos($token[1], $this->eolChar) !== false
3,330✔
1650
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
3,330✔
1651
            ) {
1652
                /*
1653
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1654
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1655
                    separately for consistency.
1656
                */
1657

1658
                $finalTokens[$newStackPtr] = [
54✔
1659
                    'code'    => T_YIELD_FROM,
54✔
1660
                    'type'    => 'T_YIELD_FROM',
54✔
1661
                    'content' => substr($token[1], 0, 5),
54✔
1662
                ];
36✔
1663
                $newStackPtr++;
54✔
1664

1665
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
54✔
1666
                $numLines   = count($tokenLines);
54✔
1667
                $newToken   = [
36✔
1668
                    'type'    => 'T_WHITESPACE',
54✔
1669
                    'code'    => T_WHITESPACE,
54✔
1670
                    'content' => '',
54✔
1671
                ];
36✔
1672

1673
                foreach ($tokenLines as $i => $line) {
54✔
1674
                    $newToken['content'] = $line;
54✔
1675
                    if ($i === ($numLines - 1)) {
54✔
1676
                        if ($line === '') {
54✔
1677
                            break;
36✔
1678
                        }
1679
                    } else {
1680
                        $newToken['content'] .= $this->eolChar;
54✔
1681
                    }
1682

1683
                    $finalTokens[$newStackPtr] = $newToken;
54✔
1684
                    $newStackPtr++;
54✔
1685
                }
1686

1687
                $finalTokens[$newStackPtr] = [
54✔
1688
                    'code'    => T_YIELD_FROM,
54✔
1689
                    'type'    => 'T_YIELD_FROM',
54✔
1690
                    'content' => substr($token[1], -4),
54✔
1691
                ];
36✔
1692
                $newStackPtr++;
54✔
1693

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

1698
                continue;
54✔
1699
            } else if (PHP_VERSION_ID >= 80300
3,330✔
1700
                && $tokenIsArray === true
3,330✔
1701
                && $token[0] === T_YIELD_FROM
3,330✔
1702
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
3,330✔
1703
                && stripos($token[1], 'yield') === 0
3,330✔
1704
            ) {
1705
                /*
1706
                    Since PHP 8.3, "yield from" allows for comments and will
1707
                    swallow the comment in the `T_YIELD_FROM` token.
1708
                    We need to split this up to allow for sniffs handling comments.
1709
                */
1710

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

1718
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
18✔
1719
                // Remove the PHP open tag token.
1720
                array_shift($yieldFromSubtokens);
18✔
1721
                // Add the "from" keyword.
1722
                $yieldFromSubtokens[] = [
18✔
1723
                    0 => T_YIELD_FROM,
18✔
1724
                    1 => substr($token[1], -4),
18✔
1725
                ];
18✔
1726

1727
                // Inject the new tokens into the token stack.
1728
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
18✔
1729
                $numTokens = count($tokens);
18✔
1730

1731
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
1732
                    StatusWriter::write("* token $stackPtr split into parts (yield from with comment)", 2);
×
1733
                }
1734

1735
                unset($yieldFromSubtokens);
18✔
1736
                continue;
18✔
1737
            }//end if
1738

1739
            /*
1740
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1741
                T_COALESCE, T_EQUAL.
1742
                So look for and combine these tokens in earlier versions.
1743
            */
1744

1745
            if ($tokenIsArray === true
3,330✔
1746
                && $token[0] === T_COALESCE
3,330✔
1747
                && isset($tokens[($stackPtr + 1)]) === true
3,330✔
1748
                && $tokens[($stackPtr + 1)][0] === '='
3,330✔
1749
            ) {
1750
                $newToken            = [];
×
1751
                $newToken['code']    = T_COALESCE_EQUAL;
×
1752
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1753
                $newToken['content'] = '??=';
×
1754
                $finalTokens[$newStackPtr] = $newToken;
×
1755

1756
                $newStackPtr++;
×
1757
                $stackPtr++;
×
1758

1759
                continue;
×
1760
            }
1761

1762
            /*
1763
                Before PHP 8, the ?-> operator was tokenized as
1764
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1765
                So look for and combine these tokens in earlier versions.
1766
            */
1767

1768
            if ($tokenIsArray === false
3,330✔
1769
                && $token[0] === '?'
3,330✔
1770
                && isset($tokens[($stackPtr + 1)]) === true
3,330✔
1771
                && is_array($tokens[($stackPtr + 1)]) === true
3,330✔
1772
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
3,330✔
1773
            ) {
1774
                $newToken            = [];
25✔
1775
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
25✔
1776
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
25✔
1777
                $newToken['content'] = '?->';
25✔
1778
                $finalTokens[$newStackPtr] = $newToken;
25✔
1779

1780
                $newStackPtr++;
25✔
1781
                $stackPtr++;
25✔
1782
                continue;
25✔
1783
            }
1784

1785
            /*
1786
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1787
                tokens split the token with a T_STRING. So look for
1788
                and change these tokens in earlier versions.
1789
            */
1790

1791
            if (PHP_VERSION_ID < 70400
3,330✔
1792
                && ($tokenIsArray === true
3,330✔
1793
                && ($token[0] === T_LNUMBER
3,330✔
1794
                || $token[0] === T_DNUMBER)
3,330✔
1795
                && isset($tokens[($stackPtr + 1)]) === true
3,330✔
1796
                && is_array($tokens[($stackPtr + 1)]) === true
3,330✔
1797
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,330✔
1798
                && $tokens[($stackPtr + 1)][1][0] === '_')
3,330✔
1799
            ) {
1800
                $newContent = $token[1];
27✔
1801
                $newType    = $token[0];
27✔
1802
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
27✔
1803
                    if (is_array($tokens[$i]) === false) {
27✔
1804
                        break;
27✔
1805
                    }
1806

1807
                    if ($tokens[$i][0] === T_LNUMBER
27✔
1808
                        || $tokens[$i][0] === T_DNUMBER
27✔
1809
                    ) {
1810
                        $newContent .= $tokens[$i][1];
27✔
1811
                        continue;
27✔
1812
                    }
1813

1814
                    if ($tokens[$i][0] === T_STRING
27✔
1815
                        && $tokens[$i][1][0] === '_'
27✔
1816
                        && ((strpos($newContent, '0x') === 0
27✔
1817
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
27✔
1818
                        || (strpos($newContent, '0x') !== 0
27✔
1819
                        && substr($newContent, -1) !== '.'
27✔
1820
                        && substr(strtolower($newContent), -1) !== 'e'
27✔
1821
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
27✔
1822
                    ) {
1823
                        $newContent .= $tokens[$i][1];
27✔
1824

1825
                        // Support floats.
1826
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
27✔
1827
                            && ($tokens[($i + 1)] === '-'
27✔
1828
                            || $tokens[($i + 1)] === '+')
27✔
1829
                        ) {
1830
                            $newContent .= $tokens[($i + 1)];
27✔
1831
                            $i++;
27✔
1832
                        }
1833

1834
                        continue;
27✔
1835
                    }//end if
1836

1837
                    break;
27✔
1838
                }//end for
1839

1840
                if ($newType === T_LNUMBER
27✔
1841
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1842
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1843
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1844
                    || (stripos($newContent, '0x') !== 0
27✔
1845
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
27✔
1846
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
27✔
1847
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1848
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
27✔
1849
                ) {
1850
                    $newType = T_DNUMBER;
27✔
1851
                }
1852

1853
                $newToken            = [];
27✔
1854
                $newToken['code']    = $newType;
27✔
1855
                $newToken['type']    = Tokens::tokenName($newType);
27✔
1856
                $newToken['content'] = $newContent;
27✔
1857
                $finalTokens[$newStackPtr] = $newToken;
27✔
1858

1859
                $newStackPtr++;
27✔
1860
                $stackPtr = ($i - 1);
27✔
1861
                continue;
27✔
1862
            }//end if
1863

1864
            /*
1865
                Backfill the T_MATCH token for PHP versions < 8.0 and
1866
                do initial correction for non-match expression T_MATCH tokens
1867
                to T_STRING for PHP >= 8.0.
1868
                A final check for non-match expression T_MATCH tokens is done
1869
                in PHP::processAdditional().
1870
            */
1871

1872
            if ($tokenIsArray === true
3,330✔
1873
                && (($token[0] === T_STRING
3,330✔
1874
                && strtolower($token[1]) === 'match')
3,198✔
1875
                || $token[0] === T_MATCH)
3,330✔
1876
            ) {
1877
                $isMatch = false;
808✔
1878
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
808✔
1879
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
808✔
1880
                        continue;
738✔
1881
                    }
1882

1883
                    if ($tokens[$x] !== '(') {
808✔
1884
                        // This is not a match expression.
1885
                        break;
422✔
1886
                    }
1887

1888
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
738✔
1889
                        // Also not a match expression.
1890
                        break;
183✔
1891
                    }
1892

1893
                    $isMatch = true;
738✔
1894
                    break;
738✔
1895
                }//end for
1896

1897
                if ($isMatch === true && $token[0] === T_STRING) {
808✔
1898
                    $newToken            = [];
246✔
1899
                    $newToken['code']    = T_MATCH;
246✔
1900
                    $newToken['type']    = 'T_MATCH';
246✔
1901
                    $newToken['content'] = $token[1];
246✔
1902

1903
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
246✔
1904
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_MATCH", 2);
×
1905
                    }
1906

1907
                    $finalTokens[$newStackPtr] = $newToken;
246✔
1908
                    $newStackPtr++;
246✔
1909
                    continue;
246✔
1910
                } else if ($isMatch === false && $token[0] === T_MATCH) {
792✔
1911
                    // PHP 8.0, match keyword, but not a match expression.
1912
                    $newToken            = [];
122✔
1913
                    $newToken['code']    = T_STRING;
122✔
1914
                    $newToken['type']    = 'T_STRING';
122✔
1915
                    $newToken['content'] = $token[1];
122✔
1916

1917
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
122✔
1918
                        StatusWriter::write("* token $stackPtr changed from T_MATCH to T_STRING", 2);
×
1919
                    }
1920

1921
                    $finalTokens[$newStackPtr] = $newToken;
122✔
1922
                    $newStackPtr++;
122✔
1923
                    continue;
122✔
1924
                }//end if
1925
            }//end if
1926

1927
            /*
1928
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
1929
                to prevent scope being set and the scope for switch default statements
1930
                breaking.
1931
            */
1932

1933
            if ($tokenIsArray === true
3,330✔
1934
                && $token[0] === T_DEFAULT
3,330✔
1935
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,330✔
1936
            ) {
1937
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,467✔
1938
                    if ($tokens[$x] === ',') {
1,467✔
1939
                        // Skip over potential trailing comma (supported in PHP).
1940
                        continue;
231✔
1941
                    }
1942

1943
                    if (is_array($tokens[$x]) === false
1,467✔
1944
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
1,467✔
1945
                    ) {
1946
                        // Non-empty, non-comma content.
1947
                        break;
1,467✔
1948
                    }
1949
                }
1950

1951
                if (isset($tokens[$x]) === true
1,467✔
1952
                    && is_array($tokens[$x]) === true
1,467✔
1953
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,467✔
1954
                ) {
1955
                    // Modify the original token stack for the double arrow so that
1956
                    // future checks can disregard the double arrow token more easily.
1957
                    // For match expression "case" statements, this is handled
1958
                    // in PHP::processAdditional().
1959
                    $tokens[$x][0] = T_MATCH_ARROW;
738✔
1960
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
738✔
1961
                        StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
1962
                    }
1963

1964
                    $newToken            = [];
738✔
1965
                    $newToken['code']    = T_MATCH_DEFAULT;
738✔
1966
                    $newToken['type']    = 'T_MATCH_DEFAULT';
738✔
1967
                    $newToken['content'] = $token[1];
738✔
1968

1969
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
738✔
1970
                        StatusWriter::write("* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT", 2);
×
1971
                    }
1972

1973
                    $finalTokens[$newStackPtr] = $newToken;
738✔
1974
                    $newStackPtr++;
738✔
1975
                    continue;
738✔
1976
                }//end if
1977
            }//end if
1978

1979
            /*
1980
                Convert ? to T_NULLABLE OR T_INLINE_THEN
1981
            */
1982

1983
            if ($tokenIsArray === false && $token[0] === '?') {
3,330✔
1984
                $newToken            = [];
1,269✔
1985
                $newToken['content'] = '?';
1,269✔
1986

1987
                // For typed constants, we only need to check the token before the ? to be sure.
1988
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,269✔
1989
                    $newToken['code'] = T_NULLABLE;
189✔
1990
                    $newToken['type'] = 'T_NULLABLE';
189✔
1991

1992
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
189✔
1993
                        StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
1994
                    }
1995

1996
                    $finalTokens[$newStackPtr] = $newToken;
189✔
1997
                    $newStackPtr++;
189✔
1998
                    continue;
189✔
1999
                }
2000

2001
                /*
2002
                 * Check if the next non-empty token is one of the tokens which can be used
2003
                 * in type declarations. If not, it's definitely a ternary.
2004
                 * At this point, the only token types which need to be taken into consideration
2005
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
2006
                 */
2007

2008
                $lastRelevantNonEmpty = null;
1,269✔
2009

2010
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,269✔
2011
                    if (is_array($tokens[$i]) === true) {
1,269✔
2012
                        $tokenType = $tokens[$i][0];
1,269✔
2013
                    } else {
2014
                        $tokenType = $tokens[$i];
937✔
2015
                    }
2016

2017
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
1,269✔
2018
                        continue;
1,269✔
2019
                    }
2020

2021
                    if (isset(Tokens::$nameTokens[$tokenType]) === true
1,269✔
2022
                        || $tokenType === T_ARRAY
1,269✔
2023
                        || $tokenType === T_NAMESPACE
1,269✔
2024
                        || $tokenType === T_NS_SEPARATOR
1,269✔
2025
                    ) {
2026
                        $lastRelevantNonEmpty = $tokenType;
937✔
2027
                        continue;
937✔
2028
                    }
2029

2030
                    if (($tokenType !== T_CALLABLE
1,269✔
2031
                        && isset($lastRelevantNonEmpty) === false)
1,269✔
2032
                        || ($lastRelevantNonEmpty === T_ARRAY
937✔
2033
                        && $tokenType === '(')
937✔
2034
                        || (isset(Tokens::$nameTokens[$lastRelevantNonEmpty]) === true
1,199✔
2035
                        && ($tokenType === T_DOUBLE_COLON
1,199✔
2036
                        || $tokenType === '('
1,199✔
2037
                        || $tokenType === ':'))
1,269✔
2038
                    ) {
2039
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,122✔
2040
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2041
                        }
2042

2043
                        $newToken['code'] = T_INLINE_THEN;
1,122✔
2044
                        $newToken['type'] = 'T_INLINE_THEN';
1,122✔
2045

2046
                        $insideInlineIf[] = $stackPtr;
1,122✔
2047

2048
                        $finalTokens[$newStackPtr] = $newToken;
1,122✔
2049
                        $newStackPtr++;
1,122✔
2050
                        continue 2;
1,122✔
2051
                    }
2052

2053
                    break;
147✔
2054
                }//end for
2055

2056
                /*
2057
                 * This can still be a nullable type or a ternary.
2058
                 * Do additional checking.
2059
                 */
2060

2061
                $prevNonEmpty     = null;
168✔
2062
                $lastSeenNonEmpty = null;
168✔
2063

2064
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
168✔
2065
                    if (is_array($tokens[$i]) === true) {
168✔
2066
                        $tokenType = $tokens[$i][0];
168✔
2067
                    } else {
2068
                        $tokenType = $tokens[$i];
168✔
2069
                    }
2070

2071
                    if ($tokenType === T_STATIC
168✔
2072
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
112✔
2073
                        || $lastSeenNonEmpty === '(')
168✔
2074
                    ) {
2075
                        $lastSeenNonEmpty = $tokenType;
×
2076
                        continue;
×
2077
                    }
2078

2079
                    if ($prevNonEmpty === null
168✔
2080
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
168✔
2081
                    ) {
2082
                        // Found the previous non-empty token.
2083
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
168✔
2084
                            $newToken['code'] = T_NULLABLE;
141✔
2085
                            $newToken['type'] = 'T_NULLABLE';
141✔
2086

2087
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
141✔
2088
                                StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2089
                            }
2090

2091
                            break;
141✔
2092
                        }
2093

2094
                        $prevNonEmpty = $tokenType;
168✔
2095
                    }
2096

2097
                    if ($tokenType === T_FUNCTION
168✔
2098
                        || $tokenType === T_FN
168✔
2099
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
168✔
2100
                        || $tokenType === T_VAR
168✔
2101
                        || $tokenType === T_READONLY
168✔
2102
                    ) {
2103
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
147✔
2104
                            StatusWriter::write("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
2105
                        }
2106

2107
                        $newToken['code'] = T_NULLABLE;
147✔
2108
                        $newToken['type'] = 'T_NULLABLE';
147✔
2109
                        break;
147✔
2110
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
168✔
2111
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
27✔
2112
                            StatusWriter::write("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
2113
                        }
2114

2115
                        $newToken['code'] = T_INLINE_THEN;
27✔
2116
                        $newToken['type'] = 'T_INLINE_THEN';
27✔
2117

2118
                        $insideInlineIf[] = $stackPtr;
27✔
2119
                        break;
27✔
2120
                    }
2121

2122
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
168✔
2123
                        $lastSeenNonEmpty = $tokenType;
168✔
2124
                    }
2125
                }//end for
2126

2127
                $finalTokens[$newStackPtr] = $newToken;
168✔
2128
                $newStackPtr++;
168✔
2129
                continue;
168✔
2130
            }//end if
2131

2132
            /*
2133
                Tokens after a double colon may look like scope openers,
2134
                such as when writing code like Foo::NAMESPACE, but they are
2135
                only ever variables or strings.
2136
            */
2137

2138
            if ($stackPtr > 1
3,330✔
2139
                && (is_array($tokens[($stackPtr - 1)]) === true
3,330✔
2140
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
3,330✔
2141
                && $tokenIsArray === true
3,330✔
2142
                && $token[0] !== T_STRING
3,330✔
2143
                && $token[0] !== T_VARIABLE
3,330✔
2144
                && $token[0] !== T_DOLLAR
3,330✔
2145
                && isset(Tokens::$emptyTokens[$token[0]]) === false
3,330✔
2146
            ) {
2147
                $newToken            = [];
×
2148
                $newToken['code']    = T_STRING;
×
2149
                $newToken['type']    = 'T_STRING';
×
2150
                $newToken['content'] = $token[1];
×
2151
                $finalTokens[$newStackPtr] = $newToken;
×
2152

2153
                $newStackPtr++;
×
2154
                continue;
×
2155
            }
2156

2157
            /*
2158
                Backfill the T_FN token for PHP versions < 7.4.
2159
            */
2160

2161
            if ($tokenIsArray === true
3,330✔
2162
                && $token[0] === T_STRING
3,330✔
2163
                && strtolower($token[1]) === 'fn'
3,330✔
2164
            ) {
2165
                // Modify the original token stack so that
2166
                // future checks (like looking for T_NULLABLE) can
2167
                // detect the T_FN token more easily.
2168
                $tokens[$stackPtr][0] = T_FN;
709✔
2169
                $token[0] = T_FN;
709✔
2170
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
709✔
2171
                    StatusWriter::write("* token $stackPtr changed from T_STRING to T_FN", 2);
×
2172
                }
2173
            }
2174

2175
            /*
2176
                This is a special condition for T_ARRAY tokens used for
2177
                function return types. We want to keep the parenthesis map clean,
2178
                so let's tag these tokens as T_STRING.
2179
            */
2180

2181
            if ($tokenIsArray === true
3,330✔
2182
                && ($token[0] === T_FUNCTION
3,330✔
2183
                || $token[0] === T_FN)
3,330✔
2184
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
3,330✔
2185
            ) {
2186
                // Go looking for the colon to start the return type hint.
2187
                // Start by finding the closing parenthesis of the function.
2188
                $parenthesisStack  = [];
2,598✔
2189
                $parenthesisCloser = false;
2,598✔
2190
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,598✔
2191
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,598✔
2192
                        $parenthesisStack[] = $x;
2,598✔
2193
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,598✔
2194
                        array_pop($parenthesisStack);
2,598✔
2195
                        if (empty($parenthesisStack) === true) {
2,598✔
2196
                            $parenthesisCloser = $x;
2,598✔
2197
                            break;
2,598✔
2198
                        }
2199
                    }
2200
                }
2201

2202
                if ($parenthesisCloser !== false) {
2,598✔
2203
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,598✔
2204
                        if (is_array($tokens[$x]) === false
2,598✔
2205
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
2,598✔
2206
                        ) {
2207
                            // Non-empty content.
2208
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,598✔
2209
                                // Found a use statement, so search ahead for the closing parenthesis.
2210
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
2211
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
2212
                                        continue(2);
57✔
2213
                                    }
2214
                                }
2215
                            }
2216

2217
                            break;
2,598✔
2218
                        }
2219
                    }
2220

2221
                    if (isset($tokens[$x]) === true
2,598✔
2222
                        && is_array($tokens[$x]) === false
2,598✔
2223
                        && $tokens[$x] === ':'
2,598✔
2224
                    ) {
2225
                        // Find the start of the return type.
2226
                        for ($x += 1; $x < $numTokens; $x++) {
1,953✔
2227
                            if (is_array($tokens[$x]) === true
1,953✔
2228
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
1,953✔
2229
                            ) {
2230
                                // Whitespace or comments before the return type.
2231
                                continue;
1,953✔
2232
                            }
2233

2234
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1,953✔
2235
                                // Found a nullable operator, so skip it.
2236
                                // But also convert the token to save the tokenizer
2237
                                // a bit of time later on.
2238
                                $tokens[$x] = [
141✔
2239
                                    T_NULLABLE,
141✔
2240
                                    '?',
141✔
2241
                                ];
94✔
2242

2243
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
141✔
2244
                                    StatusWriter::write("* token $x changed from ? to T_NULLABLE", 2);
×
2245
                                }
2246

2247
                                continue;
141✔
2248
                            }
2249

2250
                            break;
1,953✔
2251
                        }//end for
2252
                    }//end if
2253
                }//end if
2254
            }//end if
2255

2256
            /*
2257
                PHP doesn't assign a token to goto labels, so we have to.
2258
                These are just string tokens with a single colon after them. Double
2259
                colons are already tokenized and so don't interfere with this check.
2260
                But we do have to account for CASE statements, that look just like
2261
                goto labels.
2262
            */
2263

2264
            if ($tokenIsArray === true
3,330✔
2265
                && $token[0] === T_STRING
3,330✔
2266
                && (is_array($tokens[($stackPtr - 1)]) === false
3,198✔
2267
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
3,330✔
2268
            ) {
2269
                // Find next non-empty token.
2270
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,928✔
2271
                    if (is_array($tokens[$i]) === true
2,928✔
2272
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,928✔
2273
                    ) {
2274
                        continue;
2,604✔
2275
                    }
2276

2277
                    break;
2,928✔
2278
                }
2279

2280
                if (isset($tokens[$i]) === true && $tokens[$i] === ':') {
2,928✔
2281
                    // Okay, so we have a colon, now we need to make sure that this is not
2282
                    // class constant access, a ternary, enum or switch case.
2283
                    $stopTokens = [
956✔
2284
                        T_DOUBLE_COLON       => true,
1,434✔
2285
                        T_CASE               => true,
1,434✔
2286
                        T_SEMICOLON          => true,
1,434✔
2287
                        T_OPEN_TAG           => true,
1,434✔
2288
                        T_OPEN_CURLY_BRACKET => true,
1,434✔
2289
                        T_INLINE_THEN        => true,
1,434✔
2290
                        T_ENUM               => true,
1,434✔
2291
                    ];
956✔
2292

2293
                    for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,434✔
2294
                        if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,434✔
2295
                            break;
1,434✔
2296
                        }
2297
                    }
2298

2299
                    if ($finalTokens[$x]['code'] !== T_DOUBLE_COLON
1,434✔
2300
                        && $finalTokens[$x]['code'] !== T_CASE
1,434✔
2301
                        && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,434✔
2302
                        && $finalTokens[$x]['code'] !== T_ENUM
1,434✔
2303
                    ) {
2304
                        $finalTokens[$newStackPtr] = [
153✔
2305
                            'content' => $token[1],
153✔
2306
                            'code'    => T_GOTO_LABEL,
153✔
2307
                            'type'    => 'T_GOTO_LABEL',
153✔
2308
                        ];
102✔
2309

2310
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
153✔
2311
                            StatusWriter::write("* token $stackPtr changed from T_STRING to T_GOTO_LABEL", 2);
×
2312
                        }
2313

2314
                        // Modify the original token stack for the colon as potential
2315
                        // whitespace/comments between still needs to get the normal treatment.
2316
                        $tokens[$i] = [
153✔
2317
                            0 => T_GOTO_COLON,
153✔
2318
                            1 => ':',
153✔
2319
                        ];
102✔
2320

2321
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
153✔
2322
                            StatusWriter::write("* token $i changed from \":\" to T_GOTO_COLON in the original token stack", 2);
×
2323
                        }
2324

2325
                        $newStackPtr++;
153✔
2326
                        continue;
153✔
2327
                    }//end if
2328
                }//end if
2329
            }//end if
2330

2331
            /*
2332
                If this token has newlines in its content, split each line up
2333
                and create a new token for each line. We do this so it's easier
2334
                to ascertain where errors occur on a line.
2335
                Note that $token[1] is the token's content.
2336
            */
2337

2338
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
3,330✔
2339
                $tokenLines = explode($this->eolChar, $token[1]);
3,330✔
2340
                $numLines   = count($tokenLines);
3,330✔
2341
                $newToken   = [
2,222✔
2342
                    'type'    => Tokens::tokenName($token[0]),
3,330✔
2343
                    'code'    => $token[0],
3,330✔
2344
                    'content' => '',
3,330✔
2345
                ];
2,222✔
2346

2347
                for ($i = 0; $i < $numLines; $i++) {
3,330✔
2348
                    $newToken['content'] = $tokenLines[$i];
3,330✔
2349
                    if ($i === ($numLines - 1)) {
3,330✔
2350
                        if ($tokenLines[$i] === '') {
3,330✔
2351
                            break;
3,330✔
2352
                        }
2353
                    } else {
2354
                        $newToken['content'] .= $this->eolChar;
3,330✔
2355
                    }
2356

2357
                    $finalTokens[$newStackPtr] = $newToken;
3,330✔
2358
                    $newStackPtr++;
3,330✔
2359
                }
2360
            } else {
2361
                // Some T_STRING tokens should remain that way due to their context.
2362
                if ($tokenIsArray === true && $token[0] === T_STRING) {
3,330✔
2363
                    $preserveTstring = false;
2,928✔
2364

2365
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2366
                    // but the constant name should not be.
2367
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,928✔
2368
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,814✔
2369
                        || $insideConstDeclaration === true
2,928✔
2370
                    ) {
2371
                        // Find the next non-empty token.
2372
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,618✔
2373
                            if (is_array($tokens[$i]) === true
1,618✔
2374
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,618✔
2375
                            ) {
2376
                                continue;
1,477✔
2377
                            }
2378

2379
                            break;
1,618✔
2380
                        }
2381

2382
                        if ($tokens[$i] === '=') {
1,618✔
2383
                            $preserveTstring        = true;
1,423✔
2384
                            $insideConstDeclaration = false;
1,553✔
2385
                        }
2386
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,928✔
2387
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,928✔
2388
                    ) {
2389
                        $preserveTstring = true;
2,814✔
2390

2391
                        // Special case for syntax like: return new self/new parent
2392
                        // where self/parent should not be a string.
2393
                        $tokenContentLower = strtolower($token[1]);
2,814✔
2394
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,814✔
2395
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,814✔
2396
                        ) {
2397
                            $preserveTstring = false;
2,249✔
2398
                        }
2399
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,688✔
2400
                        // Function names for functions declared to return by reference.
2401
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,044✔
2402
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,044✔
2403
                                continue;
480✔
2404
                            }
2405

2406
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,044✔
2407
                                $preserveTstring = true;
480✔
2408
                            }
2409

2410
                            break;
1,044✔
2411
                        }
2412
                    } else {
2413
                        // Keywords with special PHPCS token when used as a function call.
2414
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,688✔
2415
                            if (is_array($tokens[$i]) === true
2,688✔
2416
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,688✔
2417
                            ) {
2418
                                continue;
2,283✔
2419
                            }
2420

2421
                            if ($tokens[$i][0] === '(') {
2,688✔
2422
                                $preserveTstring = true;
2,262✔
2423
                            }
2424

2425
                            break;
2,688✔
2426
                        }
2427
                    }//end if
2428

2429
                    if ($preserveTstring === true) {
2,928✔
2430
                        $finalTokens[$newStackPtr] = [
2,817✔
2431
                            'code'    => T_STRING,
2,817✔
2432
                            'type'    => 'T_STRING',
2,817✔
2433
                            'content' => $token[1],
2,817✔
2434
                        ];
1,878✔
2435

2436
                        $newStackPtr++;
2,817✔
2437
                        continue;
2,817✔
2438
                    }
2439
                }//end if
2440

2441
                $newToken = null;
3,330✔
2442
                if ($tokenIsArray === false) {
3,330✔
2443
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3,318✔
2444
                        $newToken = self::$resolveTokenCache[$token[0]];
3,318✔
2445
                    }
2446
                } else {
2447
                    $cacheKey = null;
3,330✔
2448
                    if ($token[0] === T_STRING) {
3,330✔
2449
                        $cacheKey = strtolower($token[1]);
2,685✔
2450
                    } else if ($token[0] !== T_CURLY_OPEN) {
3,330✔
2451
                        $cacheKey = $token[0];
3,330✔
2452
                    }
2453

2454
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,330✔
2455
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,330✔
2456
                        $newToken['content'] = $token[1];
3,330✔
2457
                    }
2458
                }
2459

2460
                if ($newToken === null) {
3,330✔
2461
                    $newToken = self::standardiseToken($token);
17✔
2462
                }
2463

2464
                // Convert colons that are actually the ELSE component of an
2465
                // inline IF statement.
2466
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
3,330✔
2467
                    $isInlineIf = true;
1,122✔
2468

2469
                    // Make sure this isn't a named parameter label.
2470
                    // Get the previous non-empty token.
2471
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,122✔
2472
                        if (is_array($tokens[$i]) === false
1,122✔
2473
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,122✔
2474
                        ) {
2475
                            break;
1,122✔
2476
                        }
2477
                    }
2478

2479
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,122✔
2480
                        $isInlineIf = false;
639✔
2481
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2482
                            StatusWriter::write('* token is parameter label, not T_INLINE_ELSE', 2);
×
2483
                        }
2484
                    }
2485

2486
                    if ($isInlineIf === true) {
1,122✔
2487
                        // Make sure this isn't a return type separator.
2488
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,122✔
2489
                            if (is_array($tokens[$i]) === false
1,122✔
2490
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,122✔
2491
                                && $tokens[$i][0] !== T_COMMENT
1,122✔
2492
                                && $tokens[$i][0] !== T_WHITESPACE)
1,122✔
2493
                            ) {
2494
                                break;
1,122✔
2495
                            }
2496
                        }
2497

2498
                        if ($tokens[$i] === ')') {
1,122✔
2499
                            $parenCount = 1;
639✔
2500
                            for ($i--; $i > 0; $i--) {
639✔
2501
                                if ($tokens[$i] === '(') {
639✔
2502
                                    $parenCount--;
639✔
2503
                                    if ($parenCount === 0) {
639✔
2504
                                        break;
639✔
2505
                                    }
2506
                                } else if ($tokens[$i] === ')') {
639✔
2507
                                    $parenCount++;
×
2508
                                }
2509
                            }
2510

2511
                            // We've found the open parenthesis, so if the previous
2512
                            // non-empty token is FUNCTION or USE, this is a return type.
2513
                            // Note that we need to skip T_STRING tokens here as these
2514
                            // can be function names.
2515
                            for ($i--; $i > 0; $i--) {
639✔
2516
                                if (is_array($tokens[$i]) === false
639✔
2517
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
639✔
2518
                                    && $tokens[$i][0] !== T_COMMENT
639✔
2519
                                    && $tokens[$i][0] !== T_WHITESPACE
639✔
2520
                                    && $tokens[$i][0] !== T_STRING)
639✔
2521
                                ) {
2522
                                    break;
639✔
2523
                                }
2524
                            }
2525

2526
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
639✔
2527
                                $isInlineIf = false;
639✔
2528
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2529
                                    StatusWriter::write('* token is return type, not T_INLINE_ELSE', 2);
×
2530
                                }
2531
                            }
2532
                        }//end if
2533
                    }//end if
2534

2535
                    // Check to see if this is a CASE or DEFAULT opener.
2536
                    if ($isInlineIf === true) {
1,122✔
2537
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,122✔
2538
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,122✔
2539
                            if (is_array($tokens[$i]) === true
1,122✔
2540
                                && ($tokens[$i][0] === T_CASE
1,122✔
2541
                                || $tokens[$i][0] === T_DEFAULT)
1,122✔
2542
                            ) {
2543
                                $isInlineIf = false;
×
2544
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2545
                                    StatusWriter::write('* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE', 2);
×
2546
                                }
2547

2548
                                break;
×
2549
                            }
2550

2551
                            if (is_array($tokens[$i]) === false
1,122✔
2552
                                && ($tokens[$i] === ';'
1,122✔
2553
                                || $tokens[$i] === '{'
1,122✔
2554
                                || $tokens[$i] === '}')
1,122✔
2555
                            ) {
2556
                                break;
822✔
2557
                            }
2558
                        }//end for
2559
                    }//end if
2560

2561
                    if ($isInlineIf === true) {
1,122✔
2562
                        array_pop($insideInlineIf);
1,122✔
2563
                        $newToken['code'] = T_INLINE_ELSE;
1,122✔
2564
                        $newToken['type'] = 'T_INLINE_ELSE';
1,122✔
2565

2566
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,122✔
2567
                            StatusWriter::write('* token changed from T_COLON to T_INLINE_ELSE', 2);
×
2568
                        }
2569
                    }
2570
                }//end if
2571

2572
                // This is a special condition for T_ARRAY tokens used for anything else
2573
                // but array declarations, like type hinting function arguments as
2574
                // being arrays.
2575
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2576
                // T_STRING.
2577
                if ($newToken['code'] === T_ARRAY) {
3,330✔
2578
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,515✔
2579
                        if (is_array($tokens[$i]) === false
1,515✔
2580
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,515✔
2581
                        ) {
2582
                            // Non-empty content.
2583
                            break;
1,515✔
2584
                        }
2585
                    }
2586

2587
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,515✔
2588
                        $newToken['code'] = T_STRING;
1,284✔
2589
                        $newToken['type'] = 'T_STRING';
1,284✔
2590
                    }
2591
                }
2592

2593
                // This is a special case for PHP 5.6 use function and use const
2594
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2595
                // and T_CONST.
2596
                if (($newToken['code'] === T_FUNCTION
3,330✔
2597
                    || $newToken['code'] === T_CONST)
3,330✔
2598
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
3,330✔
2599
                ) {
2600
                    $newToken['code'] = T_STRING;
141✔
2601
                    $newToken['type'] = 'T_STRING';
141✔
2602
                }
2603

2604
                // This is a special case for use groups in PHP 7+ where leaving
2605
                // the curly braces as their normal tokens would confuse
2606
                // the scope map and sniffs.
2607
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
3,330✔
2608
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
3,330✔
2609
                ) {
2610
                    $newToken['code'] = T_OPEN_USE_GROUP;
141✔
2611
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
141✔
2612
                    $insideUseGroup   = true;
141✔
2613
                }
2614

2615
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,330✔
2616
                    $newToken['code'] = T_CLOSE_USE_GROUP;
141✔
2617
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
141✔
2618
                    $insideUseGroup   = false;
141✔
2619
                }
2620

2621
                $finalTokens[$newStackPtr] = $newToken;
3,330✔
2622
                $newStackPtr++;
3,330✔
2623
            }//end if
2624
        }//end for
2625

2626
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,330✔
2627
            StatusWriter::write('*** END PHP TOKENIZING ***', 1);
×
2628
        }
2629

2630
        return $finalTokens;
3,330✔
2631

2632
    }//end tokenize()
2633

2634

2635
    /**
2636
     * Performs additional processing after main tokenizing.
2637
     *
2638
     * This additional processing checks for CASE statements that are using curly
2639
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2640
     * into T_CLOSURE when they are not standard function definitions. It also
2641
     * detects short array syntax and converts those square brackets into new tokens.
2642
     * It also corrects some usage of the static and class keywords. It also
2643
     * assigns tokens to function return types.
2644
     *
2645
     * @return void
2646
     */
2647
    protected function processAdditional()
1,722✔
2648
    {
2649
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,722✔
2650
            StatusWriter::write('*** START ADDITIONAL PHP PROCESSING ***', 1);
×
2651
        }
2652

2653
        $this->createAttributesNestingMap();
1,722✔
2654

2655
        $numTokens         = count($this->tokens);
1,722✔
2656
        $lastSeenTypeToken = $numTokens;
1,722✔
2657

2658
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,722✔
2659
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2660
            if (isset($this->tokens[$i]['scope_opener']) === true
1,722✔
2661
                && isset($this->tokens[$i]['scope_condition']) === false
1,722✔
2662
            ) {
2663
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2664
            }
2665

2666
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,722✔
2667
                /*
2668
                    Detect functions that are actually closures and
2669
                    assign them a different token.
2670
                */
2671

2672
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,434✔
2673
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,434✔
2674
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,434✔
2675
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,434✔
2676
                        ) {
2677
                            break;
1,434✔
2678
                        }
2679
                    }
2680

2681
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,434✔
2682
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,059✔
2683
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,059✔
2684
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,059✔
2685
                            $line = $this->tokens[$i]['line'];
×
2686
                            StatusWriter::write("* token $i on line $line changed from T_FUNCTION to T_CLOSURE", 1);
×
2687
                        }
2688

2689
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,059✔
2690
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
648✔
2691
                                continue;
×
2692
                            }
2693

2694
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
648✔
2695
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
648✔
2696
                                $type = $this->tokens[$x]['type'];
×
2697
                                StatusWriter::write("* cleaned $x ($type) *", 2);
×
2698
                            }
2699
                        }
2700
                    }
2701
                }//end if
2702

2703
                continue;
1,434✔
2704
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,722✔
2705
                /*
2706
                    Detect anonymous classes and assign them a different token.
2707
                */
2708

2709
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,464✔
2710
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,464✔
2711
                        break;
1,464✔
2712
                    }
2713
                }
2714

2715
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,464✔
2716
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,464✔
2717
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,437✔
2718
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,464✔
2719
                ) {
2720
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,074✔
2721
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,074✔
2722
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,074✔
2723
                        $line = $this->tokens[$i]['line'];
×
2724
                        StatusWriter::write("* token $i on line $line changed from T_CLASS to T_ANON_CLASS", 1);
×
2725
                    }
2726

2727
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,074✔
2728
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,074✔
2729
                    ) {
2730
                        $closer = $this->tokens[$x]['parenthesis_closer'];
663✔
2731

2732
                        $this->tokens[$i]['parenthesis_opener']     = $x;
663✔
2733
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
663✔
2734
                        $this->tokens[$i]['parenthesis_owner']      = $i;
663✔
2735
                        $this->tokens[$x]['parenthesis_owner']      = $i;
663✔
2736
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
663✔
2737

2738
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
663✔
2739
                            $line = $this->tokens[$i]['line'];
×
2740
                            StatusWriter::write("* added parenthesis keys to T_ANON_CLASS token $i on line $line", 2);
×
2741
                        }
2742
                    }
2743

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

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

2757
                continue;
1,464✔
2758
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,722✔
2759
                // Possible arrow function.
2760
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,143✔
2761
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,143✔
2762
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,143✔
2763
                    ) {
2764
                        // Non-whitespace content.
2765
                        break;
1,143✔
2766
                    }
2767
                }
2768

2769
                if (isset($this->tokens[$x]) === true
1,143✔
2770
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,143✔
2771
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,143✔
2772
                ) {
2773
                    $ignore  = Tokens::$emptyTokens;
1,140✔
2774
                    $ignore += Tokens::$nameTokens;
1,140✔
2775
                    $ignore += [
760✔
2776
                        T_ARRAY                  => T_ARRAY,
1,140✔
2777
                        T_CALLABLE               => T_CALLABLE,
1,140✔
2778
                        T_COLON                  => T_COLON,
1,140✔
2779
                        T_NULL                   => T_NULL,
1,140✔
2780
                        T_TRUE                   => T_TRUE,
1,140✔
2781
                        T_FALSE                  => T_FALSE,
1,140✔
2782
                        T_NULLABLE               => T_NULLABLE,
1,140✔
2783
                        T_PARENT                 => T_PARENT,
1,140✔
2784
                        T_SELF                   => T_SELF,
1,140✔
2785
                        T_STATIC                 => T_STATIC,
1,140✔
2786
                        T_TYPE_UNION             => T_TYPE_UNION,
1,140✔
2787
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,140✔
2788
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,140✔
2789
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,140✔
2790
                    ];
760✔
2791

2792
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,140✔
2793
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,140✔
2794
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,140✔
2795
                            break;
1,140✔
2796
                        }
2797
                    }
2798

2799
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,140✔
2800
                        $endTokens = [
760✔
2801
                            T_COLON                => true,
1,140✔
2802
                            T_COMMA                => true,
1,140✔
2803
                            T_SEMICOLON            => true,
1,140✔
2804
                            T_CLOSE_PARENTHESIS    => true,
1,140✔
2805
                            T_CLOSE_SQUARE_BRACKET => true,
1,140✔
2806
                            T_CLOSE_CURLY_BRACKET  => true,
1,140✔
2807
                            T_CLOSE_SHORT_ARRAY    => true,
1,140✔
2808
                            T_OPEN_TAG             => true,
1,140✔
2809
                            T_CLOSE_TAG            => true,
1,140✔
2810
                        ];
760✔
2811

2812
                        $inTernary    = false;
1,140✔
2813
                        $lastEndToken = null;
1,140✔
2814

2815
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,140✔
2816
                            // Arrow function closer should never be shared with the closer of a match
2817
                            // control structure.
2818
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,140✔
2819
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,140✔
2820
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,140✔
2821
                            ) {
2822
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
429✔
2823
                                    // Match in return value of arrow function. Move on to the next token.
2824
                                    continue;
429✔
2825
                                }
2826

2827
                                // Arrow function as return value for the last match case without trailing comma.
2828
                                if ($lastEndToken !== null) {
429✔
2829
                                    $scopeCloser = $lastEndToken;
429✔
2830
                                    break;
429✔
2831
                                }
2832

2833
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
192✔
2834
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
192✔
2835
                                        $scopeCloser = $lastNonEmpty;
192✔
2836
                                        break 2;
192✔
2837
                                    }
2838
                                }
2839
                            }
2840

2841
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,140✔
2842
                                if ($lastEndToken !== null
1,140✔
2843
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
1,003✔
2844
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
824✔
2845
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
1,003✔
2846
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
1,140✔
2847
                                ) {
2848
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
192✔
2849
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
192✔
2850
                                            $scopeCloser = $lastNonEmpty;
192✔
2851
                                            break;
192✔
2852
                                        }
2853
                                    }
2854
                                }
2855

2856
                                break;
1,140✔
2857
                            }
2858

2859
                            if ($inTernary === false
1,140✔
2860
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,140✔
2861
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,140✔
2862
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,140✔
2863
                            ) {
2864
                                // Found a nested arrow function that already has the closer set and is in
2865
                                // the same scope as us, so we can use its closer.
2866
                                break;
192✔
2867
                            }
2868

2869
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,140✔
2870
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,140✔
2871
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,140✔
2872
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,140✔
2873
                            ) {
2874
                                // We minus 1 here in case the closer can be shared with us.
2875
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
429✔
2876
                                continue;
429✔
2877
                            }
2878

2879
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,140✔
2880
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
729✔
2881
                                $lastEndToken = $scopeCloser;
729✔
2882
                                continue;
729✔
2883
                            }
2884

2885
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,140✔
2886
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
192✔
2887
                                $lastEndToken = $scopeCloser;
192✔
2888
                                continue;
192✔
2889
                            }
2890

2891
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,140✔
2892
                                $inTernary = true;
192✔
2893
                                continue;
192✔
2894
                            }
2895

2896
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,140✔
2897
                                if ($inTernary === false) {
192✔
2898
                                    break;
192✔
2899
                                }
2900

2901
                                $inTernary = false;
192✔
2902
                                continue;
192✔
2903
                            }
2904
                        }//end for
2905

2906
                        if ($scopeCloser !== $numTokens) {
1,140✔
2907
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,140✔
2908
                                $line = $this->tokens[$i]['line'];
×
2909
                                StatusWriter::write("=> token $i on line $line processed as arrow function", 1);
×
2910
                                StatusWriter::write("* scope opener set to $arrow *", 2);
×
2911
                                StatusWriter::write("* scope closer set to $scopeCloser *", 2);
×
2912
                                StatusWriter::write("* parenthesis opener set to $x *", 2);
×
2913
                                StatusWriter::write("* parenthesis closer set to $closer *", 2);
×
2914
                            }
2915

2916
                            $this->tokens[$i]['code']            = T_FN;
1,140✔
2917
                            $this->tokens[$i]['type']            = 'T_FN';
1,140✔
2918
                            $this->tokens[$i]['scope_condition'] = $i;
1,140✔
2919
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,140✔
2920
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,140✔
2921
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,140✔
2922
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,140✔
2923
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,140✔
2924

2925
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,140✔
2926
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,140✔
2927

2928
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,140✔
2929
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,140✔
2930
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,140✔
2931
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,140✔
2932
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,140✔
2933
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,140✔
2934

2935
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,140✔
2936
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,140✔
2937
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,140✔
2938
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,140✔
2939

2940
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,140✔
2941
                                $line = $this->tokens[$arrow]['line'];
×
2942
                                StatusWriter::write("* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW", 2);
×
2943
                            }
2944
                        }//end if
2945
                    }//end if
2946
                }//end if
2947

2948
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
2949
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,143✔
2950
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
606✔
2951
                        $line = $this->tokens[$i]['line'];
×
2952
                        StatusWriter::write("=> token $i on line $line is not an arrow function", 1);
×
2953
                        StatusWriter::write('* token changed from T_FN to T_STRING', 2);
×
2954
                    }
2955

2956
                    $this->tokens[$i]['code'] = T_STRING;
606✔
2957
                    $this->tokens[$i]['type'] = 'T_STRING';
964✔
2958
                }
2959
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,722✔
2960
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,179✔
2961
                    continue;
108✔
2962
                }
2963

2964
                // Unless there is a variable or a bracket before this token,
2965
                // it is the start of an array being defined using the short syntax.
2966
                $isShortArray = false;
1,179✔
2967
                $allowed      = Tokens::$nameTokens;
1,179✔
2968
                $allowed     += [
786✔
2969
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,179✔
2970
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,179✔
2971
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,179✔
2972
                    T_VARIABLE                 => T_VARIABLE,
1,179✔
2973
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,179✔
2974
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,179✔
2975
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,179✔
2976
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,179✔
2977
                ];
786✔
2978
                $allowed     += Tokens::$magicConstants;
1,179✔
2979

2980
                for ($x = ($i - 1); $x >= 0; $x--) {
1,179✔
2981
                    // If we hit a scope opener, the statement has ended
2982
                    // without finding anything, so it's probably an array
2983
                    // using PHP 7.1 short list syntax.
2984
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,179✔
2985
                        $isShortArray = true;
243✔
2986
                        break;
243✔
2987
                    }
2988

2989
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,179✔
2990
                        // Allow for control structures without braces.
2991
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,179✔
2992
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,179✔
2993
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
108✔
2994
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,179✔
2995
                        ) {
2996
                            $isShortArray = true;
1,179✔
2997
                        }
2998

2999
                        break;
1,179✔
3000
                    }
3001
                }//end for
3002

3003
                if ($isShortArray === true) {
1,179✔
3004
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,179✔
3005
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,179✔
3006

3007
                    $closer = $this->tokens[$i]['bracket_closer'];
1,179✔
3008
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,179✔
3009
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,179✔
3010
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,179✔
3011
                        $line = $this->tokens[$i]['line'];
×
3012
                        StatusWriter::write("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
3013
                        $line = $this->tokens[$closer]['line'];
×
3014
                        StatusWriter::write("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
3015
                    }
3016
                }
3017

3018
                continue;
1,179✔
3019
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,722✔
3020
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
783✔
3021
                    // Not a match expression after all.
3022
                    $this->tokens[$i]['code'] = T_STRING;
102✔
3023
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
3024

3025
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3026
                        StatusWriter::write("* token $i changed from T_MATCH to T_STRING", 2);
×
3027
                    }
3028

3029
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
3030
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
3031
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
3032
                        unset(
68✔
3033
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
3034
                            $this->tokens[$closer]['parenthesis_owner']
102✔
3035
                        );
68✔
3036
                        unset(
68✔
3037
                            $this->tokens[$i]['parenthesis_opener'],
102✔
3038
                            $this->tokens[$i]['parenthesis_closer'],
102✔
3039
                            $this->tokens[$i]['parenthesis_owner']
102✔
3040
                        );
68✔
3041

3042
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3043
                            StatusWriter::write("* cleaned parenthesis of token $i *", 2);
68✔
3044
                        }
3045
                    }
3046
                } else {
3047
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3048
                    $searchFor  = [
522✔
3049
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
783✔
3050
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
783✔
3051
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
783✔
3052
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
783✔
3053
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
783✔
3054
                    ];
522✔
3055
                    $searchFor += Tokens::$scopeOpeners;
783✔
3056

3057
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
783✔
3058
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
783✔
3059
                            continue;
783✔
3060
                        }
3061

3062
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
483✔
3063
                            $x = $this->tokens[$x]['scope_closer'];
291✔
3064
                            continue;
291✔
3065
                        }
3066

3067
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
483✔
3068
                            $x = $this->tokens[$x]['parenthesis_closer'];
483✔
3069
                            continue;
483✔
3070
                        }
3071

3072
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
483✔
3073
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
3074
                            continue;
291✔
3075
                        }
3076

3077
                        // This must be a double arrow, but make sure anyhow.
3078
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
483✔
3079
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
483✔
3080
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
483✔
3081

3082
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
483✔
3083
                                StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
3084
                            }
3085
                        }
3086
                    }//end for
3087
                }//end if
3088

3089
                continue;
783✔
3090
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,722✔
3091
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,722✔
3092
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,722✔
3093
            ) {
3094
                if ($lastSeenTypeToken < $i) {
1,719✔
3095
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3096
                    // No need to do it again.
3097
                    continue;
849✔
3098
                }
3099

3100
                /*
3101
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3102
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3103
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3104

3105
                    All type related tokens will be converted in one go as soon as this section is hit.
3106
                */
3107

3108
                $allowed  = Tokens::$nameTokens;
1,719✔
3109
                $allowed += [
1,146✔
3110
                    T_CALLABLE => T_CALLABLE,
1,719✔
3111
                    T_SELF     => T_SELF,
1,719✔
3112
                    T_PARENT   => T_PARENT,
1,719✔
3113
                    T_STATIC   => T_STATIC,
1,719✔
3114
                    T_FALSE    => T_FALSE,
1,719✔
3115
                    T_TRUE     => T_TRUE,
1,719✔
3116
                    T_NULL     => T_NULL,
1,719✔
3117
                ];
1,146✔
3118

3119
                $suspectedType       = null;
1,719✔
3120
                $typeTokenCountAfter = 0;
1,719✔
3121

3122
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,719✔
3123
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,719✔
3124
                        continue;
1,719✔
3125
                    }
3126

3127
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,719✔
3128
                        ++$typeTokenCountAfter;
1,281✔
3129
                        continue;
1,281✔
3130
                    }
3131

3132
                    if (($typeTokenCountAfter > 0
1,719✔
3133
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,719✔
3134
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,719✔
3135
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,719✔
3136
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,719✔
3137
                    ) {
3138
                        // Skip past reference and variadic indicators for parameter types.
3139
                        continue;
912✔
3140
                    }
3141

3142
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,719✔
3143
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3144
                        $suspectedType = 'property or parameter';
1,104✔
3145
                        break;
1,104✔
3146
                    }
3147

3148
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,719✔
3149
                        // Possible arrow function.
3150
                        $suspectedType = 'return';
1,140✔
3151
                        break;
1,140✔
3152
                    }
3153

3154
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,719✔
3155
                        // Possible abstract method or interface method.
3156
                        $suspectedType = 'return';
1,644✔
3157
                        break;
1,644✔
3158
                    }
3159

3160
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,719✔
3161
                        && isset($this->tokens[$x]['scope_condition']) === true
1,719✔
3162
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,719✔
3163
                    ) {
3164
                        $suspectedType = 'return';
1,422✔
3165
                        break;
1,422✔
3166
                    }
3167

3168
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,719✔
3169
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3170
                        $suspectedType = 'constant';
1,059✔
3171
                        break;
1,059✔
3172
                    }
3173

3174
                    break;
1,719✔
3175
                }//end for
3176

3177
                if (($typeTokenCountAfter === 0
1,719✔
3178
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,719✔
3179
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,719✔
3180
                    || isset($suspectedType) === false
1,719✔
3181
                ) {
3182
                    // Definitely not a union, intersection or DNF type, move on.
3183
                    continue;
1,719✔
3184
                }
3185

3186
                if ($suspectedType === 'property or parameter') {
1,680✔
3187
                    unset($allowed[T_STATIC]);
1,104✔
3188
                }
3189

3190
                $typeTokenCountBefore = 0;
1,680✔
3191
                $typeOperators        = [$i];
1,680✔
3192
                $parenthesesCount     = 0;
1,680✔
3193
                $confirmed            = false;
1,680✔
3194
                $maybeNullable        = null;
1,680✔
3195

3196
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,680✔
3197
                    ++$parenthesesCount;
1,680✔
3198
                }
3199

3200
                for ($x = ($i - 1); $x >= 0; $x--) {
1,680✔
3201
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,680✔
3202
                        continue;
1,383✔
3203
                    }
3204

3205
                    if ($suspectedType === 'property or parameter'
1,680✔
3206
                        && $this->tokens[$x]['code'] === T_STRING
1,680✔
3207
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,680✔
3208
                    ) {
3209
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3210
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3211
                        $this->tokens[$x]['code'] = T_STATIC;
300✔
3212
                        $this->tokens[$x]['type'] = 'T_STATIC';
300✔
3213

3214
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3215
                            $line = $this->tokens[$x]['line'];
×
3216
                            StatusWriter::write("* token $x on line $line changed back from T_STRING to T_STATIC", 1);
×
3217
                        }
3218
                    }
3219

3220
                    if ($suspectedType === 'property or parameter'
1,680✔
3221
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,680✔
3222
                    ) {
3223
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3224
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3225
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,104✔
3226
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,104✔
3227
                        ) {
3228
                            $confirmed = true;
1,040✔
3229
                            break;
1,040✔
3230
                        } else {
3231
                            // This may still be an arrow function which hasn't been handled yet.
3232
                            for ($y = ($x - 1); $y > 0; $y--) {
1,104✔
3233
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
1,104✔
3234
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,104✔
3235
                                ) {
3236
                                    // Non-whitespace content.
3237
                                    break;
1,104✔
3238
                                }
3239
                            }
3240

3241
                            if ($this->tokens[$y]['code'] === T_FN) {
1,104✔
3242
                                $confirmed = true;
903✔
3243
                                break;
903✔
3244
                            }
3245
                        }
3246
                    }//end if
3247

3248
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,680✔
3249
                        ++$typeTokenCountBefore;
1,383✔
3250
                        continue;
1,383✔
3251
                    }
3252

3253
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3254
                    if (($typeTokenCountBefore > 0
1,680✔
3255
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,680✔
3256
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,581✔
3257
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,680✔
3258
                    ) {
3259
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
888✔
3260
                            $maybeNullable = $x;
300✔
3261
                        }
3262

3263
                        continue;
888✔
3264
                    }
3265

3266
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,680✔
3267
                        $typeOperators[] = $x;
1,281✔
3268
                        continue;
1,281✔
3269
                    }
3270

3271
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,680✔
3272
                        ++$parenthesesCount;
1,383✔
3273
                        $typeOperators[] = $x;
1,383✔
3274
                        continue;
1,383✔
3275
                    }
3276

3277
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,680✔
3278
                        // Make sure this is the colon for a return type.
3279
                        for ($y = ($x - 1); $y > 0; $y--) {
939✔
3280
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
939✔
3281
                                break;
939✔
3282
                            }
3283
                        }
3284

3285
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
939✔
3286
                            // Definitely not a union, intersection or DNF return type, move on.
3287
                            continue 2;
300✔
3288
                        }
3289

3290
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
939✔
3291
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
839✔
3292
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
300✔
3293
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
300✔
3294
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_USE
839✔
3295
                            ) {
3296
                                $confirmed = true;
839✔
3297
                            }
3298

3299
                            break;
839✔
3300
                        }
3301

3302
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3303
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
939✔
3304
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
903✔
3305
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
903✔
3306
                                    break;
903✔
3307
                                }
3308
                            }
3309

3310
                            if ($this->tokens[$z]['code'] === T_FN) {
903✔
3311
                                $confirmed = true;
903✔
3312
                            }
3313
                        }
3314

3315
                        break;
939✔
3316
                    }//end if
3317

3318
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,680✔
3319
                        $confirmed = true;
924✔
3320
                        break;
924✔
3321
                    }
3322

3323
                    if ($suspectedType === 'property or parameter'
1,680✔
3324
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,424✔
3325
                        || $this->tokens[$x]['code'] === T_VAR
1,412✔
3326
                        || $this->tokens[$x]['code'] === T_STATIC
1,412✔
3327
                        || $this->tokens[$x]['code'] === T_READONLY
1,412✔
3328
                        || $this->tokens[$x]['code'] === T_FINAL)
1,680✔
3329
                    ) {
3330
                        // This will also confirm constructor property promotion parameters, but that's fine.
3331
                        $confirmed = true;
912✔
3332
                    }
3333

3334
                    break;
1,680✔
3335
                }//end for
3336

3337
                // Remember the last token we examined as part of the (non-)"type declaration".
3338
                $lastSeenTypeToken = $x;
1,680✔
3339

3340
                if ($confirmed === false
1,680✔
3341
                    && $suspectedType === 'property or parameter'
1,680✔
3342
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,680✔
3343
                ) {
3344
                    $parens = $this->tokens[$i]['nested_parenthesis'];
711✔
3345
                    $last   = end($parens);
711✔
3346

3347
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
711✔
3348
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
711✔
3349
                    ) {
3350
                        $confirmed = true;
711✔
3351
                    } else {
3352
                        // No parenthesis owner set, this may be an arrow function which has not yet
3353
                        // had additional processing done.
3354
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
213✔
3355
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
213✔
3356
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
213✔
3357
                                    continue;
213✔
3358
                                }
3359

3360
                                break;
213✔
3361
                            }
3362

3363
                            if ($this->tokens[$x]['code'] === T_FN) {
213✔
3364
                                for (--$x; $x >= 0; $x--) {
×
3365
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
3366
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3367
                                    ) {
3368
                                        continue;
×
3369
                                    }
3370

3371
                                    break;
×
3372
                                }
3373

3374
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3375
                                    $confirmed = true;
×
3376
                                }
3377
                            }
3378
                        }//end if
3379
                    }//end if
3380

3381
                    unset($parens, $last);
711✔
3382
                }//end if
3383

3384
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,680✔
3385
                    // Not a (valid) union, intersection or DNF type after all, move on.
3386
                    continue;
1,680✔
3387
                }
3388

3389
                foreach ($typeOperators as $x) {
1,245✔
3390
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,245✔
3391
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,245✔
3392
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,245✔
3393

3394
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,245✔
3395
                            $line = $this->tokens[$x]['line'];
×
3396
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
830✔
3397
                        }
3398
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,245✔
3399
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,245✔
3400
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,245✔
3401

3402
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,245✔
3403
                            $line = $this->tokens[$x]['line'];
×
3404
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
830✔
3405
                        }
3406
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,245✔
3407
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,245✔
3408
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,245✔
3409

3410
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,245✔
3411
                            $line = $this->tokens[$x]['line'];
×
3412
                            StatusWriter::write("* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS", 1);
830✔
3413
                        }
3414
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,245✔
3415
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,245✔
3416
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,245✔
3417

3418
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,245✔
3419
                            $line = $this->tokens[$x]['line'];
×
3420
                            StatusWriter::write("* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS", 1);
×
3421
                        }
3422
                    }//end if
3423
                }//end foreach
3424

3425
                if (isset($maybeNullable) === true) {
1,245✔
3426
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
300✔
3427
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
300✔
3428

3429
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3430
                        $line = $this->tokens[$maybeNullable]['line'];
×
3431
                        StatusWriter::write("* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE", 1);
×
3432
                    }
3433
                }
3434

3435
                continue;
1,245✔
3436
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,722✔
3437
                || $this->tokens[$i]['code'] === T_FALSE
1,722✔
3438
                || $this->tokens[$i]['code'] === T_NULL
1,722✔
3439
            ) {
3440
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,638✔
3441
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,638✔
3442
                        // Non-whitespace content.
3443
                        break;
1,638✔
3444
                    }
3445
                }
3446

3447
                if ($x !== $numTokens
1,638✔
3448
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,638✔
3449
                ) {
3450
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3451
                        $line = $this->tokens[$i]['line'];
×
3452
                        $type = $this->tokens[$i]['type'];
×
3453
                        StatusWriter::write("* token $i on line $line changed from $type to T_STRING", 1);
×
3454
                    }
3455

3456
                    $this->tokens[$i]['code'] = T_STRING;
×
3457
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3458
                }
3459
            }//end if
3460

3461
            if (($this->tokens[$i]['code'] !== T_CASE
1,722✔
3462
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,722✔
3463
                || isset($this->tokens[$i]['scope_opener']) === false
1,722✔
3464
            ) {
3465
                // Only interested in CASE and DEFAULT statements from here on in.
3466
                continue;
1,722✔
3467
            }
3468

3469
            $scopeOpener = $this->tokens[$i]['scope_opener'];
456✔
3470
            $scopeCloser = $this->tokens[$i]['scope_closer'];
456✔
3471

3472
            // If the first char after the opener is a curly brace
3473
            // and that brace has been ignored, it is actually
3474
            // opening this case statement and the opener and closer are
3475
            // probably set incorrectly.
3476
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
456✔
3477
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
456✔
3478
                    // Non-whitespace content.
3479
                    break;
456✔
3480
                }
3481
            }
3482

3483
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
456✔
3484
                // Special case for multiple CASE statements that share the same
3485
                // closer. Because we are going backwards through the file, this next
3486
                // CASE statement is already fixed, so just use its closer and don't
3487
                // worry about fixing anything.
3488
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3489
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3490
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3491
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3492
                    $newType = $this->tokens[$newCloser]['type'];
×
3493
                    $line    = $this->tokens[$i]['line'];
×
3494
                    StatusWriter::write("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3495
                }
3496

3497
                continue;
×
3498
            }
3499

3500
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
456✔
3501
                || isset($this->tokens[$x]['scope_condition']) === true
456✔
3502
            ) {
3503
                // Not a CASE/DEFAULT with a curly brace opener.
3504
                continue;
456✔
3505
            }
3506

3507
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3508
            // not whatever it already is. The opener needs to be the opening curly
3509
            // brace so everything matches up.
3510
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3511
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3512
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3513
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3514
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3515
            }
3516

3517
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3518
                $line      = $this->tokens[$i]['line'];
×
3519
                $tokenType = $this->tokens[$i]['type'];
×
3520

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

3525
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3526
                $newType = $this->tokens[$newCloser]['type'];
×
3527
                StatusWriter::write("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3528
            }
3529

3530
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3531
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3532
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3533
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3534
            }
3535

3536
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3537
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3538
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3539
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3540
            } else {
3541
                // We were using a shared closer. All tokens that were
3542
                // sharing this closer with us, except for the scope condition
3543
                // and it's opener, need to now point to the new closer.
3544
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3545
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3546
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3547
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3548
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3549
                    ) {
3550
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3551

3552
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3553
                            $line      = $this->tokens[$y]['line'];
×
3554
                            $tokenType = $this->tokens[$y]['type'];
×
3555
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3556
                            $newType   = $this->tokens[$newCloser]['type'];
×
3557
                            StatusWriter::write("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3558
                        }
3559
                    }
3560
                }
3561
            }//end if
3562

3563
            unset($this->tokens[$x]['bracket_opener']);
54✔
3564
            unset($this->tokens[$x]['bracket_closer']);
54✔
3565
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3566
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3567
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3568

3569
            // Now fix up all the tokens that think they are
3570
            // inside the CASE/DEFAULT statement when they are really outside.
3571
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3572
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3573
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3574
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3575
                        unset($this->tokens[$x]['conditions'][$num]);
×
3576

3577
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3578
                            $type     = $this->tokens[$x]['type'];
×
3579
                            $oldConds = '';
×
3580
                            foreach ($oldConditions as $condition) {
×
3581
                                $oldConds .= Tokens::tokenName($condition).',';
×
3582
                            }
3583

3584
                            $oldConds = rtrim($oldConds, ',');
×
3585

3586
                            $newConds = '';
×
3587
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3588
                                $newConds .= Tokens::tokenName($condition).',';
×
3589
                            }
3590

3591
                            $newConds = rtrim($newConds, ',');
×
3592

3593
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
3594
                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", 3);
×
3595
                        }
3596

3597
                        break;
×
3598
                    }//end if
3599
                }//end foreach
3600
            }//end for
3601
        }//end for
3602

3603
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,722✔
3604
            StatusWriter::write('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3605
        }
3606

3607
    }//end processAdditional()
574✔
3608

3609

3610
    /**
3611
     * Takes a token produced from <code>token_get_all()</code> and produces a
3612
     * more uniform token.
3613
     *
3614
     * @param string|array $token The token to convert.
3615
     *
3616
     * @return array The new token.
3617
     */
3618
    public static function standardiseToken($token)
1✔
3619
    {
3620
        if (isset($token[1]) === false) {
1✔
3621
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
1✔
3622
                return self::$resolveTokenCache[$token[0]];
1✔
3623
            }
3624
        } else {
3625
            $cacheKey = null;
1✔
3626
            if ($token[0] === T_STRING) {
1✔
3627
                $cacheKey = strtolower($token[1]);
1✔
3628
            } else if ($token[0] !== T_CURLY_OPEN) {
1✔
3629
                $cacheKey = $token[0];
1✔
3630
            }
3631

3632
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
1✔
3633
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3634
                $newToken['content'] = $token[1];
×
3635
                return $newToken;
×
3636
            }
3637
        }
3638

3639
        if (isset($token[1]) === false) {
1✔
3640
            return self::resolveSimpleToken($token[0]);
1✔
3641
        }
3642

3643
        if ($token[0] === T_STRING) {
1✔
3644
            switch ($cacheKey) {
1✔
3645
            case 'false':
1✔
3646
                $newToken['type'] = 'T_FALSE';
1✔
3647
                break;
1✔
3648
            case 'true':
1✔
3649
                $newToken['type'] = 'T_TRUE';
1✔
3650
                break;
1✔
3651
            case 'null':
1✔
3652
                $newToken['type'] = 'T_NULL';
1✔
3653
                break;
1✔
3654
            case 'self':
1✔
3655
                $newToken['type'] = 'T_SELF';
1✔
3656
                break;
1✔
3657
            case 'parent':
1✔
3658
                $newToken['type'] = 'T_PARENT';
1✔
3659
                break;
1✔
3660
            default:
3661
                $newToken['type'] = 'T_STRING';
1✔
3662
                break;
1✔
3663
            }
3664

3665
            $newToken['code'] = constant($newToken['type']);
1✔
3666

3667
            self::$resolveTokenCache[$cacheKey] = $newToken;
1✔
3668
        } else if ($token[0] === T_CURLY_OPEN) {
1✔
3669
            $newToken = [
3670
                'code' => T_OPEN_CURLY_BRACKET,
×
3671
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3672
            ];
3673
        } else {
3674
            $newToken = [
3675
                'code' => $token[0],
1✔
3676
                'type' => Tokens::tokenName($token[0]),
1✔
3677
            ];
3678

3679
            self::$resolveTokenCache[$token[0]] = $newToken;
1✔
3680
        }//end if
3681

3682
        $newToken['content'] = $token[1];
1✔
3683
        return $newToken;
1✔
3684

3685
    }//end standardiseToken()
3686

3687

3688
    /**
3689
     * Converts simple tokens into a format that conforms to complex tokens
3690
     * produced by token_get_all().
3691
     *
3692
     * Simple tokens are tokens that are not in array form when produced from
3693
     * token_get_all().
3694
     *
3695
     * @param string $token The simple token to convert.
3696
     *
3697
     * @return array The new token in array format.
3698
     */
3699
    public static function resolveSimpleToken($token)
1✔
3700
    {
3701
        $newToken = [];
1✔
3702

3703
        switch ($token) {
1✔
3704
        case '{':
1✔
3705
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1✔
3706
            break;
1✔
3707
        case '}':
1✔
3708
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1✔
3709
            break;
1✔
3710
        case '[':
1✔
3711
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1✔
3712
            break;
1✔
3713
        case ']':
1✔
3714
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1✔
3715
            break;
1✔
3716
        case '(':
1✔
3717
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
1✔
3718
            break;
1✔
3719
        case ')':
1✔
3720
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
1✔
3721
            break;
1✔
3722
        case ':':
1✔
3723
            $newToken['type'] = 'T_COLON';
1✔
3724
            break;
1✔
3725
        case '.':
1✔
3726
            $newToken['type'] = 'T_STRING_CONCAT';
1✔
3727
            break;
1✔
3728
        case ';':
1✔
3729
            $newToken['type'] = 'T_SEMICOLON';
1✔
3730
            break;
1✔
3731
        case '=':
1✔
3732
            $newToken['type'] = 'T_EQUAL';
1✔
3733
            break;
1✔
3734
        case '*':
1✔
3735
            $newToken['type'] = 'T_MULTIPLY';
1✔
3736
            break;
1✔
3737
        case '/':
1✔
3738
            $newToken['type'] = 'T_DIVIDE';
1✔
3739
            break;
1✔
3740
        case '+':
1✔
3741
            $newToken['type'] = 'T_PLUS';
1✔
3742
            break;
1✔
3743
        case '-':
1✔
3744
            $newToken['type'] = 'T_MINUS';
1✔
3745
            break;
1✔
3746
        case '%':
1✔
3747
            $newToken['type'] = 'T_MODULUS';
1✔
3748
            break;
1✔
3749
        case '^':
1✔
3750
            $newToken['type'] = 'T_BITWISE_XOR';
1✔
3751
            break;
1✔
3752
        case '&':
1✔
3753
            $newToken['type'] = 'T_BITWISE_AND';
1✔
3754
            break;
1✔
3755
        case '|':
1✔
3756
            $newToken['type'] = 'T_BITWISE_OR';
1✔
3757
            break;
1✔
3758
        case '~':
1✔
3759
            $newToken['type'] = 'T_BITWISE_NOT';
1✔
3760
            break;
1✔
3761
        case '<':
1✔
3762
            $newToken['type'] = 'T_LESS_THAN';
1✔
3763
            break;
1✔
3764
        case '>':
1✔
3765
            $newToken['type'] = 'T_GREATER_THAN';
1✔
3766
            break;
1✔
3767
        case '!':
1✔
3768
            $newToken['type'] = 'T_BOOLEAN_NOT';
1✔
3769
            break;
1✔
3770
        case ',':
1✔
3771
            $newToken['type'] = 'T_COMMA';
1✔
3772
            break;
1✔
3773
        case '@':
1✔
3774
            $newToken['type'] = 'T_ASPERAND';
1✔
3775
            break;
1✔
3776
        case '$':
1✔
3777
            $newToken['type'] = 'T_DOLLAR';
1✔
3778
            break;
1✔
3779
        case '`':
1✔
3780
            $newToken['type'] = 'T_BACKTICK';
1✔
3781
            break;
1✔
3782
        default:
3783
            $newToken['type'] = 'T_NONE';
×
3784
            break;
×
3785
        }//end switch
3786

3787
        $newToken['code']    = constant($newToken['type']);
1✔
3788
        $newToken['content'] = $token;
1✔
3789

3790
        self::$resolveTokenCache[$token] = $newToken;
1✔
3791
        return $newToken;
1✔
3792

3793
    }//end resolveSimpleToken()
3794

3795

3796
    /**
3797
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3798
     * Handle parenthesis balancing while searching for closing token
3799
     *
3800
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3801
     * @param int             $start        The starting position.
3802
     * @param string|string[] $openerTokens The opening character.
3803
     * @param string          $closerChar   The closing character.
3804
     *
3805
     * @return int|null The position of the closing token, if found. NULL otherwise.
3806
     */
3807
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3808
    {
3809
        $numTokens    = count($tokens);
51✔
3810
        $stack        = [0];
51✔
3811
        $closer       = null;
51✔
3812
        $openerTokens = (array) $openerTokens;
51✔
3813

3814
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3815
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3816
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3817
            ) {
3818
                $stack[] = $x;
51✔
3819
            } else if ($tokens[$x] === $closerChar) {
51✔
3820
                array_pop($stack);
51✔
3821
                if (empty($stack) === true) {
51✔
3822
                    $closer = $x;
51✔
3823
                    break;
51✔
3824
                }
3825
            }
3826
        }
3827

3828
        return $closer;
51✔
3829

3830
    }//end findCloser()
3831

3832

3833
    /**
3834
     * PHP 8 attributes parser for PHP < 8
3835
     * Handles single-line and multiline attributes.
3836
     *
3837
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3838
     * @param int   $stackPtr The current position in token array.
3839
     *
3840
     * @return array|null The array of parsed attribute tokens
3841
     */
3842
    private function parsePhpAttribute(array &$tokens, $stackPtr)
19✔
3843
    {
3844

3845
        $token = $tokens[$stackPtr];
19✔
3846

3847
        $commentBody = substr($token[1], 2);
19✔
3848
        $subTokens   = @token_get_all('<?php '.$commentBody);
19✔
3849

3850
        foreach ($subTokens as $i => $subToken) {
19✔
3851
            if (is_array($subToken) === true
19✔
3852
                && $subToken[0] === T_COMMENT
19✔
3853
                && strpos($subToken[1], '#[') === 0
19✔
3854
            ) {
3855
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
19✔
3856
                if ($reparsed !== null) {
19✔
3857
                    array_splice($subTokens, $i, 1, $reparsed);
19✔
3858
                } else {
3859
                    $subToken[0] = T_ATTRIBUTE;
×
3860
                }
3861
            }
3862
        }
3863

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

3866
        // Go looking for the close bracket.
3867
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3868
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
19✔
3869
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
19✔
3870
                if (is_array($token) === true) {
19✔
3871
                    $commentBody .= $token[1];
19✔
3872
                } else {
3873
                    $commentBody .= $token;
19✔
3874
                }
3875
            }
3876

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

3880
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3881
            if ($bracketCloser !== null) {
19✔
3882
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
19✔
3883
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
19✔
3884
            }
3885
        }
3886

3887
        if ($bracketCloser === null) {
19✔
3888
            return null;
19✔
3889
        }
3890

3891
        return $subTokens;
19✔
3892

3893
    }//end parsePhpAttribute()
3894

3895

3896
    /**
3897
     * Creates a map for the attributes tokens that surround other tokens.
3898
     *
3899
     * @return void
3900
     */
3901
    private function createAttributesNestingMap()
3✔
3902
    {
3903
        $map = [];
3✔
3904
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
3905
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
3906
                && $i === $this->tokens[$i]['attribute_opener']
3✔
3907
            ) {
3908
                if (empty($map) === false) {
3✔
3909
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3910
                }
3911

3912
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
3913
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
3914
                        = $this->tokens[$i]['attribute_closer'];
3✔
3915
                }
3916
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
3917
                && $i === $this->tokens[$i]['attribute_closer']
3✔
3918
            ) {
3919
                array_pop($map);
3✔
3920
                if (empty($map) === false) {
3✔
3921
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3922
                }
3923
            } else {
3924
                if (empty($map) === false) {
3✔
3925
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3926
                }
3927
            }//end if
3928
        }//end for
3929

3930
    }//end createAttributesNestingMap()
1✔
3931

3932

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

© 2026 Coveralls, Inc