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

PHPCSStandards / PHP_CodeSniffer / 14516416464

17 Apr 2025 01:09PM UTC coverage: 77.945% (+0.3%) from 77.666%
14516416464

push

github

web-flow
Merge pull request #1010 from PHPCSStandards/phpcs-4.0/feature/sq-1612-stdout-vs-stderr

(Nearly) All status, debug, and progress output is now sent to STDERR instead of STDOUT

63 of 457 new or added lines in 18 files covered. (13.79%)

1 existing line in 1 file now uncovered.

19455 of 24960 relevant lines covered (77.94%)

78.64 hits per line

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

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

10
namespace PHP_CodeSniffer\Tokenizers;
11

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

16
class PHP extends Tokenizer
17
{
18

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

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

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

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

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

506

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

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

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

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

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

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

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

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

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

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

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

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

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

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

607
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,009✔
NEW
608
                StatusWriter::writeNewline();
×
609
            }
610

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

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

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

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

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

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

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

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

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

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

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

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

691
                        break;
2,243✔
692
                    }
693

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

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

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

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

712
                        break;
627✔
713
                    }
714
                }
715

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

819
                    $stackPtr++;
×
820
                }
821

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

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

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

840
                continue;
93✔
841
            }
842

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

992
                $stackPtr = $i;
105✔
993

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1144
                $newStackPtr++;
132✔
1145

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1323
            /*
1324
                PHP 8.0 Attributes
1325
            */
1326

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

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

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

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

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

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

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

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

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

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

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

1408
                        $newStackPtr++;
690✔
1409

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

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

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

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

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

1442
                $isReadonlyKeyword = false;
996✔
1443

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1689
                continue;
×
1690
            }
1691

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1938
                $lastRelevantNonEmpty = null;
1,218✔
1939

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2027
                            break;
135✔
2028
                        }
2029

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2183
                                continue;
135✔
2184
                            }
2185

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

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

2200
            if ($tokenIsArray === true
3,009✔
2201
                && $token[0] === T_STRING
3,009✔
2202
                && isset($tokens[($stackPtr + 1)]) === true
3,009✔
2203
                && $tokens[($stackPtr + 1)] === ':'
3,009✔
2204
                && (is_array($tokens[($stackPtr - 1)]) === false
2,463✔
2205
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
3,009✔
2206
            ) {
2207
                $stopTokens = [
914✔
2208
                    T_CASE               => true,
1,371✔
2209
                    T_SEMICOLON          => true,
1,371✔
2210
                    T_OPEN_TAG           => true,
1,371✔
2211
                    T_OPEN_CURLY_BRACKET => true,
1,371✔
2212
                    T_INLINE_THEN        => true,
1,371✔
2213
                    T_ENUM               => true,
1,371✔
2214
                ];
914✔
2215

2216
                for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,371✔
2217
                    if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,371✔
2218
                        break;
1,371✔
2219
                    }
2220
                }
2221

2222
                if ($finalTokens[$x]['code'] !== T_CASE
1,371✔
2223
                    && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,371✔
2224
                    && $finalTokens[$x]['code'] !== T_ENUM
1,371✔
2225
                ) {
2226
                    $finalTokens[$newStackPtr] = [
108✔
2227
                        'content' => $token[1].':',
108✔
2228
                        'code'    => T_GOTO_LABEL,
108✔
2229
                        'type'    => 'T_GOTO_LABEL',
108✔
2230
                    ];
72✔
2231

2232
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
108✔
NEW
2233
                        StatusWriter::write("* token $stackPtr changed from T_STRING to T_GOTO_LABEL", 2);
×
NEW
2234
                        StatusWriter::write('* skipping T_COLON token '.($stackPtr + 1), 2);
×
2235
                    }
2236

2237
                    $newStackPtr++;
108✔
2238
                    $stackPtr++;
108✔
2239
                    continue;
108✔
2240
                }
2241
            }//end if
2242

2243
            /*
2244
                If this token has newlines in its content, split each line up
2245
                and create a new token for each line. We do this so it's easier
2246
                to ascertain where errors occur on a line.
2247
                Note that $token[1] is the token's content.
2248
            */
2249

2250
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
3,009✔
2251
                $tokenLines = explode($this->eolChar, $token[1]);
3,009✔
2252
                $numLines   = count($tokenLines);
3,009✔
2253
                $newToken   = [
2,006✔
2254
                    'type'    => Tokens::tokenName($token[0]),
3,009✔
2255
                    'code'    => $token[0],
3,009✔
2256
                    'content' => '',
3,009✔
2257
                ];
2,006✔
2258

2259
                for ($i = 0; $i < $numLines; $i++) {
3,009✔
2260
                    $newToken['content'] = $tokenLines[$i];
3,009✔
2261
                    if ($i === ($numLines - 1)) {
3,009✔
2262
                        if ($tokenLines[$i] === '') {
3,009✔
2263
                            break;
3,009✔
2264
                        }
2265
                    } else {
2266
                        $newToken['content'] .= $this->eolChar;
3,009✔
2267
                    }
2268

2269
                    $finalTokens[$newStackPtr] = $newToken;
3,009✔
2270
                    $newStackPtr++;
3,009✔
2271
                }
2272
            } else {
2273
                // Some T_STRING tokens should remain that way due to their context.
2274
                if ($tokenIsArray === true && $token[0] === T_STRING) {
3,009✔
2275
                    $preserveTstring = false;
2,670✔
2276

2277
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2278
                    // but the constant name should not be.
2279
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,670✔
2280
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,556✔
2281
                        || $insideConstDeclaration === true
2,670✔
2282
                    ) {
2283
                        // Find the next non-empty token.
2284
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,615✔
2285
                            if (is_array($tokens[$i]) === true
1,615✔
2286
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,615✔
2287
                            ) {
2288
                                continue;
1,480✔
2289
                            }
2290

2291
                            break;
1,615✔
2292
                        }
2293

2294
                        if ($tokens[$i] === '=') {
1,615✔
2295
                            $preserveTstring        = true;
1,426✔
2296
                            $insideConstDeclaration = false;
1,552✔
2297
                        }
2298
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,670✔
2299
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,670✔
2300
                    ) {
2301
                        $preserveTstring = true;
2,556✔
2302

2303
                        // Special case for syntax like: return new self/new parent
2304
                        // where self/parent should not be a string.
2305
                        $tokenContentLower = strtolower($token[1]);
2,556✔
2306
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,556✔
2307
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,556✔
2308
                        ) {
2309
                            $preserveTstring = false;
2,077✔
2310
                        }
2311
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,640✔
2312
                        // Function names for functions declared to return by reference.
2313
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,047✔
2314
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,047✔
2315
                                continue;
480✔
2316
                            }
2317

2318
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,047✔
2319
                                $preserveTstring = true;
480✔
2320
                            }
2321

2322
                            break;
1,047✔
2323
                        }
2324
                    } else {
2325
                        // Keywords with special PHPCS token when used as a function call.
2326
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,640✔
2327
                            if (is_array($tokens[$i]) === true
2,640✔
2328
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,640✔
2329
                            ) {
2330
                                continue;
2,235✔
2331
                            }
2332

2333
                            if ($tokens[$i][0] === '(') {
2,640✔
2334
                                $preserveTstring = true;
2,214✔
2335
                            }
2336

2337
                            break;
2,640✔
2338
                        }
2339
                    }//end if
2340

2341
                    if ($preserveTstring === true) {
2,670✔
2342
                        $finalTokens[$newStackPtr] = [
2,559✔
2343
                            'code'    => T_STRING,
2,559✔
2344
                            'type'    => 'T_STRING',
2,559✔
2345
                            'content' => $token[1],
2,559✔
2346
                        ];
1,706✔
2347

2348
                        $newStackPtr++;
2,559✔
2349
                        continue;
2,559✔
2350
                    }
2351
                }//end if
2352

2353
                $newToken = null;
3,009✔
2354
                if ($tokenIsArray === false) {
3,009✔
2355
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
2,997✔
2356
                        $newToken = self::$resolveTokenCache[$token[0]];
2,997✔
2357
                    }
2358
                } else {
2359
                    $cacheKey = null;
3,009✔
2360
                    if ($token[0] === T_STRING) {
3,009✔
2361
                        $cacheKey = strtolower($token[1]);
2,637✔
2362
                    } else if ($token[0] !== T_CURLY_OPEN) {
3,009✔
2363
                        $cacheKey = $token[0];
3,009✔
2364
                    }
2365

2366
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,009✔
2367
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,009✔
2368
                        $newToken['content'] = $token[1];
3,009✔
2369
                    }
2370
                }
2371

2372
                if ($newToken === null) {
3,009✔
2373
                    $newToken = self::standardiseToken($token);
18✔
2374
                }
2375

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

2381
                    // Make sure this isn't a named parameter label.
2382
                    // Get the previous non-empty token.
2383
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,077✔
2384
                        if (is_array($tokens[$i]) === false
1,077✔
2385
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,077✔
2386
                        ) {
2387
                            break;
1,077✔
2388
                        }
2389
                    }
2390

2391
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,077✔
2392
                        $isInlineIf = false;
639✔
2393
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
NEW
2394
                            StatusWriter::write('* token is parameter label, not T_INLINE_ELSE', 2);
×
2395
                        }
2396
                    }
2397

2398
                    if ($isInlineIf === true) {
1,077✔
2399
                        // Make sure this isn't a return type separator.
2400
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,077✔
2401
                            if (is_array($tokens[$i]) === false
1,077✔
2402
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,077✔
2403
                                && $tokens[$i][0] !== T_COMMENT
1,077✔
2404
                                && $tokens[$i][0] !== T_WHITESPACE)
1,077✔
2405
                            ) {
2406
                                break;
1,077✔
2407
                            }
2408
                        }
2409

2410
                        if ($tokens[$i] === ')') {
1,077✔
2411
                            $parenCount = 1;
639✔
2412
                            for ($i--; $i > 0; $i--) {
639✔
2413
                                if ($tokens[$i] === '(') {
639✔
2414
                                    $parenCount--;
639✔
2415
                                    if ($parenCount === 0) {
639✔
2416
                                        break;
639✔
2417
                                    }
2418
                                } else if ($tokens[$i] === ')') {
639✔
2419
                                    $parenCount++;
×
2420
                                }
2421
                            }
2422

2423
                            // We've found the open parenthesis, so if the previous
2424
                            // non-empty token is FUNCTION or USE, this is a return type.
2425
                            // Note that we need to skip T_STRING tokens here as these
2426
                            // can be function names.
2427
                            for ($i--; $i > 0; $i--) {
639✔
2428
                                if (is_array($tokens[$i]) === false
639✔
2429
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
639✔
2430
                                    && $tokens[$i][0] !== T_COMMENT
639✔
2431
                                    && $tokens[$i][0] !== T_WHITESPACE
639✔
2432
                                    && $tokens[$i][0] !== T_STRING)
639✔
2433
                                ) {
2434
                                    break;
639✔
2435
                                }
2436
                            }
2437

2438
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
639✔
2439
                                $isInlineIf = false;
639✔
2440
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
NEW
2441
                                    StatusWriter::write('* token is return type, not T_INLINE_ELSE', 2);
×
2442
                                }
2443
                            }
2444
                        }//end if
2445
                    }//end if
2446

2447
                    // Check to see if this is a CASE or DEFAULT opener.
2448
                    if ($isInlineIf === true) {
1,077✔
2449
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,077✔
2450
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,077✔
2451
                            if (is_array($tokens[$i]) === true
1,077✔
2452
                                && ($tokens[$i][0] === T_CASE
1,077✔
2453
                                || $tokens[$i][0] === T_DEFAULT)
1,077✔
2454
                            ) {
2455
                                $isInlineIf = false;
×
2456
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
2457
                                    StatusWriter::write('* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE', 2);
×
2458
                                }
2459

2460
                                break;
×
2461
                            }
2462

2463
                            if (is_array($tokens[$i]) === false
1,077✔
2464
                                && ($tokens[$i] === ';'
1,077✔
2465
                                || $tokens[$i] === '{'
1,077✔
2466
                                || $tokens[$i] === '}')
1,077✔
2467
                            ) {
2468
                                break;
822✔
2469
                            }
2470
                        }//end for
2471
                    }//end if
2472

2473
                    if ($isInlineIf === true) {
1,077✔
2474
                        array_pop($insideInlineIf);
1,077✔
2475
                        $newToken['code'] = T_INLINE_ELSE;
1,077✔
2476
                        $newToken['type'] = 'T_INLINE_ELSE';
1,077✔
2477

2478
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,077✔
NEW
2479
                            StatusWriter::write('* token changed from T_COLON to T_INLINE_ELSE', 2);
×
2480
                        }
2481
                    }
2482
                }//end if
2483

2484
                // This is a special condition for T_ARRAY tokens used for anything else
2485
                // but array declarations, like type hinting function arguments as
2486
                // being arrays.
2487
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2488
                // T_STRING.
2489
                if ($newToken['code'] === T_ARRAY) {
3,009✔
2490
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,518✔
2491
                        if (is_array($tokens[$i]) === false
1,518✔
2492
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,518✔
2493
                        ) {
2494
                            // Non-empty content.
2495
                            break;
1,518✔
2496
                        }
2497
                    }
2498

2499
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,518✔
2500
                        $newToken['code'] = T_STRING;
1,287✔
2501
                        $newToken['type'] = 'T_STRING';
1,287✔
2502
                    }
2503
                }
2504

2505
                // This is a special case for PHP 5.6 use function and use const
2506
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2507
                // and T_CONST.
2508
                if (($newToken['code'] === T_FUNCTION
3,009✔
2509
                    || $newToken['code'] === T_CONST)
3,009✔
2510
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
3,009✔
2511
                ) {
2512
                    $newToken['code'] = T_STRING;
135✔
2513
                    $newToken['type'] = 'T_STRING';
135✔
2514
                }
2515

2516
                // This is a special case for use groups in PHP 7+ where leaving
2517
                // the curly braces as their normal tokens would confuse
2518
                // the scope map and sniffs.
2519
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
3,009✔
2520
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
3,009✔
2521
                ) {
2522
                    $newToken['code'] = T_OPEN_USE_GROUP;
135✔
2523
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
135✔
2524
                    $insideUseGroup   = true;
135✔
2525
                }
2526

2527
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,009✔
2528
                    $newToken['code'] = T_CLOSE_USE_GROUP;
135✔
2529
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
135✔
2530
                    $insideUseGroup   = false;
135✔
2531
                }
2532

2533
                $finalTokens[$newStackPtr] = $newToken;
3,009✔
2534
                $newStackPtr++;
3,009✔
2535
            }//end if
2536
        }//end for
2537

2538
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,009✔
NEW
2539
            StatusWriter::write('*** END PHP TOKENIZING ***', 1);
×
2540
        }
2541

2542
        return $finalTokens;
3,009✔
2543

2544
    }//end tokenize()
2545

2546

2547
    /**
2548
     * Performs additional processing after main tokenizing.
2549
     *
2550
     * This additional processing checks for CASE statements that are using curly
2551
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2552
     * into T_CLOSURE when they are not standard function definitions. It also
2553
     * detects short array syntax and converts those square brackets into new tokens.
2554
     * It also corrects some usage of the static and class keywords. It also
2555
     * assigns tokens to function return types.
2556
     *
2557
     * @return void
2558
     */
2559
    protected function processAdditional()
1,707✔
2560
    {
2561
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,707✔
NEW
2562
            StatusWriter::write('*** START ADDITIONAL PHP PROCESSING ***', 1);
×
2563
        }
2564

2565
        $this->createAttributesNestingMap();
1,707✔
2566

2567
        $numTokens         = count($this->tokens);
1,707✔
2568
        $lastSeenTypeToken = $numTokens;
1,707✔
2569

2570
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,707✔
2571
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2572
            if (isset($this->tokens[$i]['scope_opener']) === true
1,707✔
2573
                && isset($this->tokens[$i]['scope_condition']) === false
1,707✔
2574
            ) {
2575
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2576
            }
2577

2578
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,707✔
2579
                /*
2580
                    Detect functions that are actually closures and
2581
                    assign them a different token.
2582
                */
2583

2584
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,428✔
2585
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,416✔
2586
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,416✔
2587
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,416✔
2588
                        ) {
2589
                            break;
1,416✔
2590
                        }
2591
                    }
2592

2593
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,416✔
2594
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,053✔
2595
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,053✔
2596
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,053✔
2597
                            $line = $this->tokens[$i]['line'];
×
NEW
2598
                            StatusWriter::write("* token $i on line $line changed from T_FUNCTION to T_CLOSURE", 1);
×
2599
                        }
2600

2601
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,053✔
2602
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
642✔
2603
                                continue;
×
2604
                            }
2605

2606
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
642✔
2607
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
642✔
2608
                                $type = $this->tokens[$x]['type'];
×
NEW
2609
                                StatusWriter::write("* cleaned $x ($type) *", 2);
×
2610
                            }
2611
                        }
2612
                    }
2613
                }//end if
2614

2615
                continue;
1,428✔
2616
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,707✔
2617
                /*
2618
                    Detect anonymous classes and assign them a different token.
2619
                */
2620

2621
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,470✔
2622
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,470✔
2623
                        break;
1,470✔
2624
                    }
2625
                }
2626

2627
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,470✔
2628
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,470✔
2629
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,443✔
2630
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,470✔
2631
                ) {
2632
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,068✔
2633
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,068✔
2634
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,068✔
2635
                        $line = $this->tokens[$i]['line'];
×
NEW
2636
                        StatusWriter::write("* token $i on line $line changed from T_CLASS to T_ANON_CLASS", 1);
×
2637
                    }
2638

2639
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,068✔
2640
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,068✔
2641
                    ) {
2642
                        $closer = $this->tokens[$x]['parenthesis_closer'];
657✔
2643

2644
                        $this->tokens[$i]['parenthesis_opener']     = $x;
657✔
2645
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
657✔
2646
                        $this->tokens[$i]['parenthesis_owner']      = $i;
657✔
2647
                        $this->tokens[$x]['parenthesis_owner']      = $i;
657✔
2648
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
657✔
2649

2650
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
657✔
2651
                            $line = $this->tokens[$i]['line'];
×
NEW
2652
                            StatusWriter::write("* added parenthesis keys to T_ANON_CLASS token $i on line $line", 2);
×
2653
                        }
2654
                    }
2655

2656
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,068✔
2657
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,068✔
2658
                            continue;
×
2659
                        }
2660

2661
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,068✔
2662
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,068✔
2663
                            $type = $this->tokens[$x]['type'];
×
NEW
2664
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
2665
                        }
2666
                    }
2667
                }//end if
2668

2669
                continue;
1,470✔
2670
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,707✔
2671
                // Possible arrow function.
2672
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,137✔
2673
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,137✔
2674
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,137✔
2675
                    ) {
2676
                        // Non-whitespace content.
2677
                        break;
1,137✔
2678
                    }
2679
                }
2680

2681
                if (isset($this->tokens[$x]) === true
1,137✔
2682
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,137✔
2683
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,137✔
2684
                ) {
2685
                    $ignore  = Tokens::$emptyTokens;
1,134✔
2686
                    $ignore += [
756✔
2687
                        T_ARRAY                  => T_ARRAY,
1,134✔
2688
                        T_CALLABLE               => T_CALLABLE,
1,134✔
2689
                        T_COLON                  => T_COLON,
1,134✔
2690
                        T_NAMESPACE              => T_NAMESPACE,
1,134✔
2691
                        T_NS_SEPARATOR           => T_NS_SEPARATOR,
1,134✔
2692
                        T_NULL                   => T_NULL,
1,134✔
2693
                        T_TRUE                   => T_TRUE,
1,134✔
2694
                        T_FALSE                  => T_FALSE,
1,134✔
2695
                        T_NULLABLE               => T_NULLABLE,
1,134✔
2696
                        T_PARENT                 => T_PARENT,
1,134✔
2697
                        T_SELF                   => T_SELF,
1,134✔
2698
                        T_STATIC                 => T_STATIC,
1,134✔
2699
                        T_STRING                 => T_STRING,
1,134✔
2700
                        T_TYPE_UNION             => T_TYPE_UNION,
1,134✔
2701
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,134✔
2702
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,134✔
2703
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,134✔
2704
                    ];
756✔
2705

2706
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,134✔
2707
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,134✔
2708
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,134✔
2709
                            break;
1,134✔
2710
                        }
2711
                    }
2712

2713
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,134✔
2714
                        $endTokens = [
756✔
2715
                            T_COLON                => true,
1,134✔
2716
                            T_COMMA                => true,
1,134✔
2717
                            T_SEMICOLON            => true,
1,134✔
2718
                            T_CLOSE_PARENTHESIS    => true,
1,134✔
2719
                            T_CLOSE_SQUARE_BRACKET => true,
1,134✔
2720
                            T_CLOSE_CURLY_BRACKET  => true,
1,134✔
2721
                            T_CLOSE_SHORT_ARRAY    => true,
1,134✔
2722
                            T_OPEN_TAG             => true,
1,134✔
2723
                            T_CLOSE_TAG            => true,
1,134✔
2724
                        ];
756✔
2725

2726
                        $inTernary    = false;
1,134✔
2727
                        $lastEndToken = null;
1,134✔
2728

2729
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,134✔
2730
                            // Arrow function closer should never be shared with the closer of a match
2731
                            // control structure.
2732
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,134✔
2733
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,134✔
2734
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,134✔
2735
                            ) {
2736
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
423✔
2737
                                    // Match in return value of arrow function. Move on to the next token.
2738
                                    continue;
423✔
2739
                                }
2740

2741
                                // Arrow function as return value for the last match case without trailing comma.
2742
                                if ($lastEndToken !== null) {
423✔
2743
                                    $scopeCloser = $lastEndToken;
423✔
2744
                                    break;
423✔
2745
                                }
2746

2747
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2748
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2749
                                        $scopeCloser = $lastNonEmpty;
186✔
2750
                                        break 2;
186✔
2751
                                    }
2752
                                }
2753
                            }
2754

2755
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,134✔
2756
                                if ($lastEndToken !== null
1,134✔
2757
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
997✔
2758
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
818✔
2759
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
997✔
2760
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
1,134✔
2761
                                ) {
2762
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2763
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2764
                                            $scopeCloser = $lastNonEmpty;
186✔
2765
                                            break;
186✔
2766
                                        }
2767
                                    }
2768
                                }
2769

2770
                                break;
1,134✔
2771
                            }
2772

2773
                            if ($inTernary === false
1,134✔
2774
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,134✔
2775
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,134✔
2776
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,134✔
2777
                            ) {
2778
                                // Found a nested arrow function that already has the closer set and is in
2779
                                // the same scope as us, so we can use its closer.
2780
                                break;
186✔
2781
                            }
2782

2783
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,134✔
2784
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,134✔
2785
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,134✔
2786
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,134✔
2787
                            ) {
2788
                                // We minus 1 here in case the closer can be shared with us.
2789
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
423✔
2790
                                continue;
423✔
2791
                            }
2792

2793
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,134✔
2794
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
723✔
2795
                                $lastEndToken = $scopeCloser;
723✔
2796
                                continue;
723✔
2797
                            }
2798

2799
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,134✔
2800
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
186✔
2801
                                $lastEndToken = $scopeCloser;
186✔
2802
                                continue;
186✔
2803
                            }
2804

2805
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,134✔
2806
                                $inTernary = true;
186✔
2807
                                continue;
186✔
2808
                            }
2809

2810
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,134✔
2811
                                if ($inTernary === false) {
186✔
2812
                                    break;
186✔
2813
                                }
2814

2815
                                $inTernary = false;
186✔
2816
                                continue;
186✔
2817
                            }
2818
                        }//end for
2819

2820
                        if ($scopeCloser !== $numTokens) {
1,134✔
2821
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,134✔
2822
                                $line = $this->tokens[$i]['line'];
×
NEW
2823
                                StatusWriter::write("=> token $i on line $line processed as arrow function", 1);
×
NEW
2824
                                StatusWriter::write("* scope opener set to $arrow *", 2);
×
NEW
2825
                                StatusWriter::write("* scope closer set to $scopeCloser *", 2);
×
NEW
2826
                                StatusWriter::write("* parenthesis opener set to $x *", 2);
×
NEW
2827
                                StatusWriter::write("* parenthesis closer set to $closer *", 2);
×
2828
                            }
2829

2830
                            $this->tokens[$i]['code']            = T_FN;
1,134✔
2831
                            $this->tokens[$i]['type']            = 'T_FN';
1,134✔
2832
                            $this->tokens[$i]['scope_condition'] = $i;
1,134✔
2833
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,134✔
2834
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,134✔
2835
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,134✔
2836
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,134✔
2837
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,134✔
2838

2839
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,134✔
2840
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,134✔
2841

2842
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,134✔
2843
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,134✔
2844
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,134✔
2845
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,134✔
2846
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,134✔
2847
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,134✔
2848

2849
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,134✔
2850
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,134✔
2851
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,134✔
2852
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,134✔
2853

2854
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,134✔
2855
                                $line = $this->tokens[$arrow]['line'];
×
NEW
2856
                                StatusWriter::write("* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW", 2);
×
2857
                            }
2858
                        }//end if
2859
                    }//end if
2860
                }//end if
2861

2862
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
2863
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,137✔
2864
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
600✔
2865
                        $line = $this->tokens[$i]['line'];
×
NEW
2866
                        StatusWriter::write("=> token $i on line $line is not an arrow function", 1);
×
NEW
2867
                        StatusWriter::write('* token changed from T_FN to T_STRING', 2);
×
2868
                    }
2869

2870
                    $this->tokens[$i]['code'] = T_STRING;
600✔
2871
                    $this->tokens[$i]['type'] = 'T_STRING';
958✔
2872
                }
2873
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,707✔
2874
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,164✔
2875
                    continue;
99✔
2876
                }
2877

2878
                // Unless there is a variable or a bracket before this token,
2879
                // it is the start of an array being defined using the short syntax.
2880
                $isShortArray = false;
1,164✔
2881
                $allowed      = [
776✔
2882
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,164✔
2883
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,164✔
2884
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,164✔
2885
                    T_VARIABLE                 => T_VARIABLE,
1,164✔
2886
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,164✔
2887
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,164✔
2888
                    T_STRING                   => T_STRING,
1,164✔
2889
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,164✔
2890
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,164✔
2891
                ];
776✔
2892
                $allowed     += Tokens::$magicConstants;
1,164✔
2893

2894
                for ($x = ($i - 1); $x >= 0; $x--) {
1,164✔
2895
                    // If we hit a scope opener, the statement has ended
2896
                    // without finding anything, so it's probably an array
2897
                    // using PHP 7.1 short list syntax.
2898
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,164✔
2899
                        $isShortArray = true;
234✔
2900
                        break;
234✔
2901
                    }
2902

2903
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,164✔
2904
                        // Allow for control structures without braces.
2905
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,164✔
2906
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,164✔
2907
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
99✔
2908
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,164✔
2909
                        ) {
2910
                            $isShortArray = true;
1,164✔
2911
                        }
2912

2913
                        break;
1,164✔
2914
                    }
2915
                }//end for
2916

2917
                if ($isShortArray === true) {
1,164✔
2918
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,164✔
2919
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,164✔
2920

2921
                    $closer = $this->tokens[$i]['bracket_closer'];
1,164✔
2922
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,164✔
2923
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,164✔
2924
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,164✔
2925
                        $line = $this->tokens[$i]['line'];
×
NEW
2926
                        StatusWriter::write("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
2927
                        $line = $this->tokens[$closer]['line'];
×
NEW
2928
                        StatusWriter::write("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
2929
                    }
2930
                }
2931

2932
                continue;
1,164✔
2933
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,707✔
2934
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
777✔
2935
                    // Not a match expression after all.
2936
                    $this->tokens[$i]['code'] = T_STRING;
102✔
2937
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
2938

2939
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
NEW
2940
                        StatusWriter::write("* token $i changed from T_MATCH to T_STRING", 2);
×
2941
                    }
2942

2943
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
2944
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
2945
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
2946
                        unset(
68✔
2947
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
2948
                            $this->tokens[$closer]['parenthesis_owner']
102✔
2949
                        );
68✔
2950
                        unset(
68✔
2951
                            $this->tokens[$i]['parenthesis_opener'],
102✔
2952
                            $this->tokens[$i]['parenthesis_closer'],
102✔
2953
                            $this->tokens[$i]['parenthesis_owner']
102✔
2954
                        );
68✔
2955

2956
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
2957
                            StatusWriter::write("* cleaned parenthesis of token $i *", 2);
68✔
2958
                        }
2959
                    }
2960
                } else {
2961
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
2962
                    $searchFor  = [
518✔
2963
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
777✔
2964
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
777✔
2965
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
777✔
2966
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
777✔
2967
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
777✔
2968
                    ];
518✔
2969
                    $searchFor += Tokens::$scopeOpeners;
777✔
2970

2971
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
777✔
2972
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
777✔
2973
                            continue;
777✔
2974
                        }
2975

2976
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
477✔
2977
                            $x = $this->tokens[$x]['scope_closer'];
291✔
2978
                            continue;
291✔
2979
                        }
2980

2981
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
477✔
2982
                            $x = $this->tokens[$x]['parenthesis_closer'];
477✔
2983
                            continue;
477✔
2984
                        }
2985

2986
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
477✔
2987
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
2988
                            continue;
291✔
2989
                        }
2990

2991
                        // This must be a double arrow, but make sure anyhow.
2992
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
477✔
2993
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
477✔
2994
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
477✔
2995

2996
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
477✔
NEW
2997
                                StatusWriter::write("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 2);
×
2998
                            }
2999
                        }
3000
                    }//end for
3001
                }//end if
3002

3003
                continue;
777✔
3004
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,707✔
3005
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,707✔
3006
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,707✔
3007
            ) {
3008
                if ($lastSeenTypeToken < $i) {
1,704✔
3009
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3010
                    // No need to do it again.
3011
                    continue;
849✔
3012
                }
3013

3014
                /*
3015
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3016
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3017
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3018

3019
                    All type related tokens will be converted in one go as soon as this section is hit.
3020
                */
3021

3022
                $allowed = [
1,136✔
3023
                    T_STRING       => T_STRING,
1,704✔
3024
                    T_CALLABLE     => T_CALLABLE,
1,704✔
3025
                    T_SELF         => T_SELF,
1,704✔
3026
                    T_PARENT       => T_PARENT,
1,704✔
3027
                    T_STATIC       => T_STATIC,
1,704✔
3028
                    T_FALSE        => T_FALSE,
1,704✔
3029
                    T_TRUE         => T_TRUE,
1,704✔
3030
                    T_NULL         => T_NULL,
1,704✔
3031
                    T_NAMESPACE    => T_NAMESPACE,
1,704✔
3032
                    T_NS_SEPARATOR => T_NS_SEPARATOR,
1,704✔
3033
                ];
1,136✔
3034

3035
                $suspectedType       = null;
1,704✔
3036
                $typeTokenCountAfter = 0;
1,704✔
3037

3038
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,704✔
3039
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,704✔
3040
                        continue;
1,704✔
3041
                    }
3042

3043
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,704✔
3044
                        ++$typeTokenCountAfter;
1,275✔
3045
                        continue;
1,275✔
3046
                    }
3047

3048
                    if (($typeTokenCountAfter > 0
1,704✔
3049
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,704✔
3050
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,704✔
3051
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,700✔
3052
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,704✔
3053
                    ) {
3054
                        // Skip past reference and variadic indicators for parameter types.
3055
                        continue;
912✔
3056
                    }
3057

3058
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,704✔
3059
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3060
                        $suspectedType = 'property or parameter';
1,098✔
3061
                        break;
1,098✔
3062
                    }
3063

3064
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,704✔
3065
                        // Possible arrow function.
3066
                        $suspectedType = 'return';
1,134✔
3067
                        break;
1,134✔
3068
                    }
3069

3070
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,704✔
3071
                        // Possible abstract method or interface method.
3072
                        $suspectedType = 'return';
1,629✔
3073
                        break;
1,629✔
3074
                    }
3075

3076
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,704✔
3077
                        && isset($this->tokens[$x]['scope_condition']) === true
1,704✔
3078
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,704✔
3079
                    ) {
3080
                        $suspectedType = 'return';
1,416✔
3081
                        break;
1,416✔
3082
                    }
3083

3084
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,704✔
3085
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3086
                        $suspectedType = 'constant';
1,059✔
3087
                        break;
1,059✔
3088
                    }
3089

3090
                    break;
1,704✔
3091
                }//end for
3092

3093
                if (($typeTokenCountAfter === 0
1,704✔
3094
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,704✔
3095
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,704✔
3096
                    || isset($suspectedType) === false
1,704✔
3097
                ) {
3098
                    // Definitely not a union, intersection or DNF type, move on.
3099
                    continue;
1,704✔
3100
                }
3101

3102
                if ($suspectedType === 'property or parameter') {
1,665✔
3103
                    unset($allowed[T_STATIC]);
1,098✔
3104
                }
3105

3106
                $typeTokenCountBefore = 0;
1,665✔
3107
                $typeOperators        = [$i];
1,665✔
3108
                $parenthesesCount     = 0;
1,665✔
3109
                $confirmed            = false;
1,665✔
3110
                $maybeNullable        = null;
1,665✔
3111

3112
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,665✔
3113
                    ++$parenthesesCount;
1,665✔
3114
                }
3115

3116
                for ($x = ($i - 1); $x >= 0; $x--) {
1,665✔
3117
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,665✔
3118
                        continue;
1,377✔
3119
                    }
3120

3121
                    if ($suspectedType === 'property or parameter'
1,665✔
3122
                        && $this->tokens[$x]['code'] === T_STRING
1,665✔
3123
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,665✔
3124
                    ) {
3125
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3126
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3127
                        $this->tokens[$x]['code'] = T_STATIC;
300✔
3128
                        $this->tokens[$x]['type'] = 'T_STATIC';
300✔
3129

3130
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3131
                            $line = $this->tokens[$x]['line'];
×
NEW
3132
                            StatusWriter::write("* token $x on line $line changed back from T_STRING to T_STATIC", 1);
×
3133
                        }
3134
                    }
3135

3136
                    if ($suspectedType === 'property or parameter'
1,665✔
3137
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,665✔
3138
                    ) {
3139
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3140
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3141
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,098✔
3142
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,098✔
3143
                        ) {
3144
                            $confirmed = true;
1,098✔
3145
                            break;
1,098✔
3146
                        } else {
3147
                            // This may still be an arrow function which hasn't been handled yet.
3148
                            for ($y = ($x - 1); $y > 0; $y--) {
1,098✔
3149
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
1,098✔
3150
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,098✔
3151
                                ) {
3152
                                    // Non-whitespace content.
3153
                                    break;
1,098✔
3154
                                }
3155
                            }
3156

3157
                            if ($this->tokens[$y]['code'] === T_FN) {
1,098✔
3158
                                $confirmed = true;
897✔
3159
                                break;
897✔
3160
                            }
3161
                        }
3162
                    }//end if
3163

3164
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,665✔
3165
                        ++$typeTokenCountBefore;
1,377✔
3166
                        continue;
1,377✔
3167
                    }
3168

3169
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3170
                    if (($typeTokenCountBefore > 0
1,665✔
3171
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,665✔
3172
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,569✔
3173
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,665✔
3174
                    ) {
3175
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
888✔
3176
                            $maybeNullable = $x;
300✔
3177
                        }
3178

3179
                        continue;
888✔
3180
                    }
3181

3182
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,665✔
3183
                        $typeOperators[] = $x;
1,275✔
3184
                        continue;
1,275✔
3185
                    }
3186

3187
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,665✔
3188
                        ++$parenthesesCount;
1,377✔
3189
                        $typeOperators[] = $x;
1,377✔
3190
                        continue;
1,377✔
3191
                    }
3192

3193
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,665✔
3194
                        // Make sure this is the colon for a return type.
3195
                        for ($y = ($x - 1); $y > 0; $y--) {
933✔
3196
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
933✔
3197
                                break;
933✔
3198
                            }
3199
                        }
3200

3201
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
933✔
3202
                            // Definitely not a union, intersection or DNF return type, move on.
3203
                            continue 2;
300✔
3204
                        }
3205

3206
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
933✔
3207
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
897✔
3208
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
300✔
3209
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
897✔
3210
                            ) {
3211
                                $confirmed = true;
897✔
3212
                            }
3213

3214
                            break;
897✔
3215
                        }
3216

3217
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3218
                        // Closure use tokens won't be parentheses owners until PHPCS 4.0.
3219
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
933✔
3220
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
897✔
3221
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
897✔
3222
                                    break;
897✔
3223
                                }
3224
                            }
3225

3226
                            if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
897✔
3227
                                $confirmed = true;
897✔
3228
                            }
3229
                        }
3230

3231
                        break;
933✔
3232
                    }//end if
3233

3234
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,665✔
3235
                        $confirmed = true;
924✔
3236
                        break;
924✔
3237
                    }
3238

3239
                    if ($suspectedType === 'property or parameter'
1,665✔
3240
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,414✔
3241
                        || $this->tokens[$x]['code'] === T_VAR
1,402✔
3242
                        || $this->tokens[$x]['code'] === T_STATIC
1,402✔
3243
                        || $this->tokens[$x]['code'] === T_READONLY
1,402✔
3244
                        || $this->tokens[$x]['code'] === T_FINAL)
1,665✔
3245
                    ) {
3246
                        // This will also confirm constructor property promotion parameters, but that's fine.
3247
                        $confirmed = true;
912✔
3248
                    }
3249

3250
                    break;
1,665✔
3251
                }//end for
3252

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

3256
                if ($confirmed === false
1,665✔
3257
                    && $suspectedType === 'property or parameter'
1,665✔
3258
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,665✔
3259
                ) {
3260
                    $parens = $this->tokens[$i]['nested_parenthesis'];
711✔
3261
                    $last   = end($parens);
711✔
3262

3263
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
711✔
3264
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
711✔
3265
                    ) {
3266
                        $confirmed = true;
711✔
3267
                    } else {
3268
                        // No parenthesis owner set, this may be an arrow function which has not yet
3269
                        // had additional processing done.
3270
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
213✔
3271
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
213✔
3272
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
213✔
3273
                                    continue;
213✔
3274
                                }
3275

3276
                                break;
213✔
3277
                            }
3278

3279
                            if ($this->tokens[$x]['code'] === T_FN) {
213✔
3280
                                for (--$x; $x >= 0; $x--) {
×
3281
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
3282
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3283
                                    ) {
3284
                                        continue;
×
3285
                                    }
3286

3287
                                    break;
×
3288
                                }
3289

3290
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3291
                                    $confirmed = true;
×
3292
                                }
3293
                            }
3294
                        }//end if
3295
                    }//end if
3296

3297
                    unset($parens, $last);
711✔
3298
                }//end if
3299

3300
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,665✔
3301
                    // Not a (valid) union, intersection or DNF type after all, move on.
3302
                    continue;
1,665✔
3303
                }
3304

3305
                foreach ($typeOperators as $x) {
1,239✔
3306
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,239✔
3307
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,239✔
3308
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,239✔
3309

3310
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3311
                            $line = $this->tokens[$x]['line'];
×
3312
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
826✔
3313
                        }
3314
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,239✔
3315
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,239✔
3316
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,239✔
3317

3318
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3319
                            $line = $this->tokens[$x]['line'];
×
3320
                            StatusWriter::write("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
826✔
3321
                        }
3322
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,239✔
3323
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,239✔
3324
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,239✔
3325

3326
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3327
                            $line = $this->tokens[$x]['line'];
×
3328
                            StatusWriter::write("* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS", 1);
826✔
3329
                        }
3330
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,239✔
3331
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,239✔
3332
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,239✔
3333

3334
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,239✔
3335
                            $line = $this->tokens[$x]['line'];
×
NEW
3336
                            StatusWriter::write("* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS", 1);
×
3337
                        }
3338
                    }//end if
3339
                }//end foreach
3340

3341
                if (isset($maybeNullable) === true) {
1,239✔
3342
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
300✔
3343
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
300✔
3344

3345
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
300✔
3346
                        $line = $this->tokens[$maybeNullable]['line'];
×
NEW
3347
                        StatusWriter::write("* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE", 1);
×
3348
                    }
3349
                }
3350

3351
                continue;
1,239✔
3352
            } else if ($this->tokens[$i]['code'] === T_STATIC) {
1,707✔
3353
                for ($x = ($i - 1); $x > 0; $x--) {
1,344✔
3354
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,344✔
3355
                        break;
1,344✔
3356
                    }
3357
                }
3358

3359
                if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
1,344✔
3360
                    $this->tokens[$i]['code'] = T_STRING;
×
3361
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3362

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

3369
                continue;
1,344✔
3370
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,707✔
3371
                || $this->tokens[$i]['code'] === T_FALSE
1,707✔
3372
                || $this->tokens[$i]['code'] === T_NULL
1,707✔
3373
            ) {
3374
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,623✔
3375
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,623✔
3376
                        // Non-whitespace content.
3377
                        break;
1,623✔
3378
                    }
3379
                }
3380

3381
                if ($x !== $numTokens
1,623✔
3382
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,623✔
3383
                ) {
3384
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3385
                        $line = $this->tokens[$i]['line'];
×
3386
                        $type = $this->tokens[$i]['type'];
×
NEW
3387
                        StatusWriter::write("* token $i on line $line changed from $type to T_STRING", 1);
×
3388
                    }
3389

3390
                    $this->tokens[$i]['code'] = T_STRING;
×
3391
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3392
                }
3393
            }//end if
3394

3395
            if (($this->tokens[$i]['code'] !== T_CASE
1,707✔
3396
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,707✔
3397
                || isset($this->tokens[$i]['scope_opener']) === false
1,707✔
3398
            ) {
3399
                // Only interested in CASE and DEFAULT statements from here on in.
3400
                continue;
1,707✔
3401
            }
3402

3403
            $scopeOpener = $this->tokens[$i]['scope_opener'];
456✔
3404
            $scopeCloser = $this->tokens[$i]['scope_closer'];
456✔
3405

3406
            // If the first char after the opener is a curly brace
3407
            // and that brace has been ignored, it is actually
3408
            // opening this case statement and the opener and closer are
3409
            // probably set incorrectly.
3410
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
456✔
3411
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
456✔
3412
                    // Non-whitespace content.
3413
                    break;
456✔
3414
                }
3415
            }
3416

3417
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
456✔
3418
                // Special case for multiple CASE statements that share the same
3419
                // closer. Because we are going backwards through the file, this next
3420
                // CASE statement is already fixed, so just use its closer and don't
3421
                // worry about fixing anything.
3422
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3423
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3424
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3425
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3426
                    $newType = $this->tokens[$newCloser]['type'];
×
3427
                    $line    = $this->tokens[$i]['line'];
×
NEW
3428
                    StatusWriter::write("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3429
                }
3430

3431
                continue;
×
3432
            }
3433

3434
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
456✔
3435
                || isset($this->tokens[$x]['scope_condition']) === true
456✔
3436
            ) {
3437
                // Not a CASE/DEFAULT with a curly brace opener.
3438
                continue;
456✔
3439
            }
3440

3441
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3442
            // not whatever it already is. The opener needs to be the opening curly
3443
            // brace so everything matches up.
3444
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3445
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3446
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3447
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3448
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3449
            }
3450

3451
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3452
                $line      = $this->tokens[$i]['line'];
×
3453
                $tokenType = $this->tokens[$i]['type'];
×
3454

3455
                $oldType = $this->tokens[$scopeOpener]['type'];
×
3456
                $newType = $this->tokens[$x]['type'];
×
NEW
3457
                StatusWriter::write("* token $i ($tokenType) on line $line opener changed from $scopeOpener ($oldType) to $x ($newType)", 1);
×
3458

3459
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3460
                $newType = $this->tokens[$newCloser]['type'];
×
NEW
3461
                StatusWriter::write("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3462
            }
3463

3464
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3465
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3466
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3467
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3468
            }
3469

3470
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3471
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3472
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3473
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3474
            } else {
3475
                // We were using a shared closer. All tokens that were
3476
                // sharing this closer with us, except for the scope condition
3477
                // and it's opener, need to now point to the new closer.
3478
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3479
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3480
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3481
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3482
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3483
                    ) {
3484
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3485

3486
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3487
                            $line      = $this->tokens[$y]['line'];
×
3488
                            $tokenType = $this->tokens[$y]['type'];
×
3489
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3490
                            $newType   = $this->tokens[$newCloser]['type'];
×
NEW
3491
                            StatusWriter::write("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3492
                        }
3493
                    }
3494
                }
3495
            }//end if
3496

3497
            unset($this->tokens[$x]['bracket_opener']);
54✔
3498
            unset($this->tokens[$x]['bracket_closer']);
54✔
3499
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3500
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3501
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3502

3503
            // Now fix up all the tokens that think they are
3504
            // inside the CASE/DEFAULT statement when they are really outside.
3505
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3506
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3507
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3508
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3509
                        unset($this->tokens[$x]['conditions'][$num]);
×
3510

3511
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3512
                            $type     = $this->tokens[$x]['type'];
×
3513
                            $oldConds = '';
×
3514
                            foreach ($oldConditions as $condition) {
×
3515
                                $oldConds .= Tokens::tokenName($condition).',';
×
3516
                            }
3517

3518
                            $oldConds = rtrim($oldConds, ',');
×
3519

3520
                            $newConds = '';
×
3521
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3522
                                $newConds .= Tokens::tokenName($condition).',';
×
3523
                            }
3524

3525
                            $newConds = rtrim($newConds, ',');
×
3526

NEW
3527
                            StatusWriter::write("* cleaned $x ($type) *", 2);
×
NEW
3528
                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", 3);
×
3529
                        }
3530

3531
                        break;
×
3532
                    }//end if
3533
                }//end foreach
3534
            }//end for
3535
        }//end for
3536

3537
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,707✔
NEW
3538
            StatusWriter::write('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3539
        }
3540

3541
    }//end processAdditional()
569✔
3542

3543

3544
    /**
3545
     * Takes a token produced from <code>token_get_all()</code> and produces a
3546
     * more uniform token.
3547
     *
3548
     * @param string|array $token The token to convert.
3549
     *
3550
     * @return array The new token.
3551
     */
3552
    public static function standardiseToken($token)
1✔
3553
    {
3554
        if (isset($token[1]) === false) {
1✔
3555
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
1✔
3556
                return self::$resolveTokenCache[$token[0]];
1✔
3557
            }
3558
        } else {
3559
            $cacheKey = null;
1✔
3560
            if ($token[0] === T_STRING) {
1✔
3561
                $cacheKey = strtolower($token[1]);
1✔
3562
            } else if ($token[0] !== T_CURLY_OPEN) {
1✔
3563
                $cacheKey = $token[0];
1✔
3564
            }
3565

3566
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
1✔
3567
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3568
                $newToken['content'] = $token[1];
×
3569
                return $newToken;
×
3570
            }
3571
        }
3572

3573
        if (isset($token[1]) === false) {
1✔
3574
            return self::resolveSimpleToken($token[0]);
1✔
3575
        }
3576

3577
        if ($token[0] === T_STRING) {
1✔
3578
            switch ($cacheKey) {
1✔
3579
            case 'false':
1✔
3580
                $newToken['type'] = 'T_FALSE';
1✔
3581
                break;
1✔
3582
            case 'true':
1✔
3583
                $newToken['type'] = 'T_TRUE';
1✔
3584
                break;
1✔
3585
            case 'null':
1✔
3586
                $newToken['type'] = 'T_NULL';
1✔
3587
                break;
1✔
3588
            case 'self':
1✔
3589
                $newToken['type'] = 'T_SELF';
1✔
3590
                break;
1✔
3591
            case 'parent':
1✔
3592
                $newToken['type'] = 'T_PARENT';
1✔
3593
                break;
1✔
3594
            default:
3595
                $newToken['type'] = 'T_STRING';
1✔
3596
                break;
1✔
3597
            }
3598

3599
            $newToken['code'] = constant($newToken['type']);
1✔
3600

3601
            self::$resolveTokenCache[$cacheKey] = $newToken;
1✔
3602
        } else if ($token[0] === T_CURLY_OPEN) {
1✔
3603
            $newToken = [
3604
                'code' => T_OPEN_CURLY_BRACKET,
×
3605
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3606
            ];
3607
        } else {
3608
            $newToken = [
3609
                'code' => $token[0],
1✔
3610
                'type' => Tokens::tokenName($token[0]),
1✔
3611
            ];
3612

3613
            self::$resolveTokenCache[$token[0]] = $newToken;
1✔
3614
        }//end if
3615

3616
        $newToken['content'] = $token[1];
1✔
3617
        return $newToken;
1✔
3618

3619
    }//end standardiseToken()
3620

3621

3622
    /**
3623
     * Converts simple tokens into a format that conforms to complex tokens
3624
     * produced by token_get_all().
3625
     *
3626
     * Simple tokens are tokens that are not in array form when produced from
3627
     * token_get_all().
3628
     *
3629
     * @param string $token The simple token to convert.
3630
     *
3631
     * @return array The new token in array format.
3632
     */
3633
    public static function resolveSimpleToken($token)
1✔
3634
    {
3635
        $newToken = [];
1✔
3636

3637
        switch ($token) {
1✔
3638
        case '{':
1✔
3639
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1✔
3640
            break;
1✔
3641
        case '}':
1✔
3642
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1✔
3643
            break;
1✔
3644
        case '[':
1✔
3645
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1✔
3646
            break;
1✔
3647
        case ']':
1✔
3648
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1✔
3649
            break;
1✔
3650
        case '(':
1✔
3651
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
1✔
3652
            break;
1✔
3653
        case ')':
1✔
3654
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
1✔
3655
            break;
1✔
3656
        case ':':
1✔
3657
            $newToken['type'] = 'T_COLON';
1✔
3658
            break;
1✔
3659
        case '.':
1✔
3660
            $newToken['type'] = 'T_STRING_CONCAT';
1✔
3661
            break;
1✔
3662
        case ';':
1✔
3663
            $newToken['type'] = 'T_SEMICOLON';
1✔
3664
            break;
1✔
3665
        case '=':
1✔
3666
            $newToken['type'] = 'T_EQUAL';
1✔
3667
            break;
1✔
3668
        case '*':
1✔
3669
            $newToken['type'] = 'T_MULTIPLY';
1✔
3670
            break;
1✔
3671
        case '/':
1✔
3672
            $newToken['type'] = 'T_DIVIDE';
1✔
3673
            break;
1✔
3674
        case '+':
1✔
3675
            $newToken['type'] = 'T_PLUS';
1✔
3676
            break;
1✔
3677
        case '-':
1✔
3678
            $newToken['type'] = 'T_MINUS';
1✔
3679
            break;
1✔
3680
        case '%':
1✔
3681
            $newToken['type'] = 'T_MODULUS';
1✔
3682
            break;
1✔
3683
        case '^':
1✔
3684
            $newToken['type'] = 'T_BITWISE_XOR';
1✔
3685
            break;
1✔
3686
        case '&':
1✔
3687
            $newToken['type'] = 'T_BITWISE_AND';
1✔
3688
            break;
1✔
3689
        case '|':
1✔
3690
            $newToken['type'] = 'T_BITWISE_OR';
1✔
3691
            break;
1✔
3692
        case '~':
1✔
3693
            $newToken['type'] = 'T_BITWISE_NOT';
1✔
3694
            break;
1✔
3695
        case '<':
1✔
3696
            $newToken['type'] = 'T_LESS_THAN';
1✔
3697
            break;
1✔
3698
        case '>':
1✔
3699
            $newToken['type'] = 'T_GREATER_THAN';
1✔
3700
            break;
1✔
3701
        case '!':
1✔
3702
            $newToken['type'] = 'T_BOOLEAN_NOT';
1✔
3703
            break;
1✔
3704
        case ',':
1✔
3705
            $newToken['type'] = 'T_COMMA';
1✔
3706
            break;
1✔
3707
        case '@':
1✔
3708
            $newToken['type'] = 'T_ASPERAND';
1✔
3709
            break;
1✔
3710
        case '$':
1✔
3711
            $newToken['type'] = 'T_DOLLAR';
1✔
3712
            break;
1✔
3713
        case '`':
1✔
3714
            $newToken['type'] = 'T_BACKTICK';
1✔
3715
            break;
1✔
3716
        default:
3717
            $newToken['type'] = 'T_NONE';
×
3718
            break;
×
3719
        }//end switch
3720

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

3724
        self::$resolveTokenCache[$token] = $newToken;
1✔
3725
        return $newToken;
1✔
3726

3727
    }//end resolveSimpleToken()
3728

3729

3730
    /**
3731
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3732
     * Handle parenthesis balancing while searching for closing token
3733
     *
3734
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3735
     * @param int             $start        The starting position.
3736
     * @param string|string[] $openerTokens The opening character.
3737
     * @param string          $closerChar   The closing character.
3738
     *
3739
     * @return int|null The position of the closing token, if found. NULL otherwise.
3740
     */
3741
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3742
    {
3743
        $numTokens    = count($tokens);
51✔
3744
        $stack        = [0];
51✔
3745
        $closer       = null;
51✔
3746
        $openerTokens = (array) $openerTokens;
51✔
3747

3748
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3749
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3750
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3751
            ) {
3752
                $stack[] = $x;
51✔
3753
            } else if ($tokens[$x] === $closerChar) {
51✔
3754
                array_pop($stack);
51✔
3755
                if (empty($stack) === true) {
51✔
3756
                    $closer = $x;
51✔
3757
                    break;
51✔
3758
                }
3759
            }
3760
        }
3761

3762
        return $closer;
51✔
3763

3764
    }//end findCloser()
3765

3766

3767
    /**
3768
     * PHP 8 attributes parser for PHP < 8
3769
     * Handles single-line and multiline attributes.
3770
     *
3771
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3772
     * @param int   $stackPtr The current position in token array.
3773
     *
3774
     * @return array|null The array of parsed attribute tokens
3775
     */
3776
    private function parsePhpAttribute(array &$tokens, $stackPtr)
19✔
3777
    {
3778

3779
        $token = $tokens[$stackPtr];
19✔
3780

3781
        $commentBody = substr($token[1], 2);
19✔
3782
        $subTokens   = @token_get_all('<?php '.$commentBody);
19✔
3783

3784
        foreach ($subTokens as $i => $subToken) {
19✔
3785
            if (is_array($subToken) === true
19✔
3786
                && $subToken[0] === T_COMMENT
19✔
3787
                && strpos($subToken[1], '#[') === 0
19✔
3788
            ) {
3789
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
19✔
3790
                if ($reparsed !== null) {
19✔
3791
                    array_splice($subTokens, $i, 1, $reparsed);
19✔
3792
                } else {
3793
                    $subToken[0] = T_ATTRIBUTE;
×
3794
                }
3795
            }
3796
        }
3797

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

3800
        // Go looking for the close bracket.
3801
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3802
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
19✔
3803
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
19✔
3804
                if (is_array($token) === true) {
19✔
3805
                    $commentBody .= $token[1];
19✔
3806
                } else {
3807
                    $commentBody .= $token;
19✔
3808
                }
3809
            }
3810

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

3814
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3815
            if ($bracketCloser !== null) {
19✔
3816
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
19✔
3817
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
19✔
3818
            }
3819
        }
3820

3821
        if ($bracketCloser === null) {
19✔
3822
            return null;
19✔
3823
        }
3824

3825
        return $subTokens;
19✔
3826

3827
    }//end parsePhpAttribute()
3828

3829

3830
    /**
3831
     * Creates a map for the attributes tokens that surround other tokens.
3832
     *
3833
     * @return void
3834
     */
3835
    private function createAttributesNestingMap()
3✔
3836
    {
3837
        $map = [];
3✔
3838
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
3839
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
3840
                && $i === $this->tokens[$i]['attribute_opener']
3✔
3841
            ) {
3842
                if (empty($map) === false) {
3✔
3843
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3844
                }
3845

3846
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
3847
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
3848
                        = $this->tokens[$i]['attribute_closer'];
3✔
3849
                }
3850
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
3851
                && $i === $this->tokens[$i]['attribute_closer']
3✔
3852
            ) {
3853
                array_pop($map);
3✔
3854
                if (empty($map) === false) {
3✔
3855
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3856
                }
3857
            } else {
3858
                if (empty($map) === false) {
3✔
3859
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3860
                }
3861
            }//end if
3862
        }//end for
3863

3864
    }//end createAttributesNestingMap()
1✔
3865

3866

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