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

PHPCSStandards / PHP_CodeSniffer / 15036337869

15 May 2025 04:03AM UTC coverage: 78.375% (-0.2%) from 78.556%
15036337869

Pull #856

github

web-flow
Merge 93f570b46 into f5e7943d0
Pull Request #856: [Doc] Cover all errors of PEAR ClassDeclaration

25112 of 32041 relevant lines covered (78.37%)

69.4 hits per line

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

87.41
/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

15
class PHP extends Tokenizer
16
{
17

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

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

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

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

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

509

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

530
        $tokens      = @token_get_all($string);
3,001✔
531
        $finalTokens = [];
3,001✔
532

533
        $newStackPtr       = 0;
3,001✔
534
        $numTokens         = count($tokens);
3,001✔
535
        $lastNotEmptyToken = 0;
3,001✔
536

537
        $insideInlineIf         = [];
3,001✔
538
        $insideUseGroup         = false;
3,001✔
539
        $insideConstDeclaration = false;
3,001✔
540

541
        $commentTokenizer = new Comment();
3,001✔
542

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

549
            $token        = (array) $tokens[$stackPtr];
3,001✔
550
            $tokenIsArray = isset($token[1]);
3,001✔
551

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

562
                echo "\tProcess token ";
×
563
                if ($tokenIsArray === true) {
×
564
                    echo "[$stackPtr]";
×
565
                } else {
566
                    echo " $stackPtr ";
×
567
                }
568

569
                echo ": $type => $content";
×
570
            }//end if
571

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

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

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

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

609
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,001✔
610
                echo PHP_EOL;
×
611
            }
612

613
            /*
614
                Before PHP 5.5, the yield keyword was tokenized as
615
                T_STRING. So look for and change this token in
616
                earlier versions.
617
            */
618

619
            if (PHP_VERSION_ID < 50500
3,001✔
620
                && $tokenIsArray === true
3,001✔
621
                && $token[0] === T_STRING
3,001✔
622
                && strtolower($token[1]) === 'yield'
3,001✔
623
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,001✔
624
            ) {
975✔
625
                // Could still be a context sensitive keyword or "yield from" and potentially multi-line,
626
                // so adjust the token stack in place.
627
                $token[0] = T_YIELD;
422✔
628

629
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
422✔
630
                    echo "\t\t* token $stackPtr changed from T_STRING to T_YIELD".PHP_EOL;
×
631
                }
632
            }
422✔
633

634
            /*
635
                Tokenize context sensitive keyword as string when it should be string.
636
            */
637

638
            if ($tokenIsArray === true
2,026✔
639
                && isset(Tokens::$contextSensitiveKeywords[$token[0]]) === true
3,001✔
640
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,901✔
641
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
2,801✔
642
                || $insideConstDeclaration === true)
2,901✔
643
            ) {
975✔
644
                if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,381✔
645
                    $preserveKeyword = false;
2,361✔
646

647
                    // `new class`, and `new static` should be preserved.
648
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,361✔
649
                        && ($token[0] === T_CLASS
2,102✔
650
                        || $token[0] === T_STATIC)
2,128✔
651
                    ) {
750✔
652
                        $preserveKeyword = true;
1,492✔
653
                    }
472✔
654

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

667
                        if (is_array($tokens[$i]) === true && $tokens[$i][0] === T_CLASS) {
173✔
668
                            $preserveKeyword = true;
173✔
669
                        }
670
                    }
671

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

679
                    // `namespace\` should be preserved
680
                    if ($token[0] === T_NAMESPACE) {
2,361✔
681
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
697✔
682
                            if (is_array($tokens[$i]) === false) {
697✔
683
                                break;
519✔
684
                            }
685

686
                            if (isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true) {
697✔
687
                                continue;
519✔
688
                            }
689

690
                            if ($tokens[$i][0] === T_NS_SEPARATOR) {
524✔
691
                                $preserveKeyword = true;
524✔
692
                            }
243✔
693

694
                            break;
524✔
695
                        }
696
                    }
243✔
697
                }//end if
750✔
698

699
                // Types in typed constants should not be touched, but the constant name should be.
700
                if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,381✔
701
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,381✔
702
                    || $insideConstDeclaration === true
1,611✔
703
                ) {
770✔
704
                    $preserveKeyword = true;
2,162✔
705

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

714
                        break;
2,162✔
715
                    }
716

717
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
2,162✔
718
                        $preserveKeyword        = false;
757✔
719
                        $insideConstDeclaration = false;
757✔
720
                    }
228✔
721
                }//end if
671✔
722

723
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,381✔
724
                    $preserveKeyword = true;
667✔
725

726
                    for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
667✔
727
                        if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
667✔
728
                            continue;
579✔
729
                        }
730

731
                        if ($finalTokens[$i]['code'] === T_FUNCTION) {
667✔
732
                            $preserveKeyword = false;
579✔
733
                        }
193✔
734

735
                        break;
667✔
736
                    }
737
                }
218✔
738

739
                if ($preserveKeyword === false) {
2,381✔
740
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
989✔
741
                        $type = Tokens::tokenName($token[0]);
×
742
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
743
                    }
744

745
                    $finalTokens[$newStackPtr] = [
989✔
746
                        'code'    => T_STRING,
989✔
747
                        'type'    => 'T_STRING',
989✔
748
                        'content' => $token[1],
989✔
749
                    ];
361✔
750

751
                    $newStackPtr++;
989✔
752
                    continue;
989✔
753
                }
754
            }//end if
696✔
755

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

761
            if ($tokenIsArray === true && $token[0] === T_CONST) {
3,001✔
762
                $insideConstDeclaration = true;
2,419✔
763
            }
781✔
764

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

769
            if ($insideConstDeclaration === true
2,026✔
770
                && $tokenIsArray === false
3,001✔
771
                && ($token[0] === '=' || $token[0] === ';')
3,001✔
772
            ) {
975✔
773
                $insideConstDeclaration = false;
1,120✔
774
            }
431✔
775

776
            /*
777
                Special case for `static` used as a function name, i.e. `static()`.
778

779
                Note: this may incorrectly change the static keyword directly before a DNF property type.
780
                If so, this will be caught and corrected for in the additional processing.
781
            */
782

783
            if ($tokenIsArray === true
2,026✔
784
                && $token[0] === T_STATIC
3,001✔
785
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
3,001✔
786
            ) {
975✔
787
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,023✔
788
                    if (is_array($tokens[$i]) === true
2,023✔
789
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,023✔
790
                    ) {
649✔
791
                        continue;
1,384✔
792
                    }
793

794
                    if ($tokens[$i][0] === '(') {
2,023✔
795
                        $finalTokens[$newStackPtr] = [
519✔
796
                            'code'    => T_STRING,
519✔
797
                            'type'    => 'T_STRING',
519✔
798
                            'content' => $token[1],
519✔
799
                        ];
173✔
800

801
                        $newStackPtr++;
519✔
802
                        continue 2;
519✔
803
                    }
804

805
                    break;
2,023✔
806
                }
807
            }//end if
649✔
808

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

815
            if (PHP_VERSION_ID < 70400
3,001✔
816
                && $tokenIsArray === true
3,001✔
817
                // PHP < 7.4 with short open tags off.
818
                && (($stackPtr === ($numTokens - 1)
3,001✔
819
                && $token[0] === T_INLINE_HTML
3,001✔
820
                && stripos($token[1], '<?php') === 0)
1,988✔
821
                // PHP < 7.4 with short open tags on.
1,013✔
822
                || ($stackPtr === ($numTokens - 2)
3,001✔
823
                && $token[0] === T_OPEN_TAG
3,001✔
824
                && $token[1] === '<?'
3,001✔
825
                && is_array($tokens[($stackPtr + 1)]) === true
3,001✔
826
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,001✔
827
                && strtolower($tokens[($stackPtr + 1)][1]) === 'php'))
3,001✔
828
            ) {
975✔
829
                if ($token[0] === T_INLINE_HTML) {
4✔
830
                    $finalTokens[$newStackPtr] = [
2✔
831
                        'code'    => T_OPEN_TAG,
2✔
832
                        'type'    => 'T_OPEN_TAG',
2✔
833
                        'content' => $token[1],
2✔
834
                    ];
835
                } else {
2✔
836
                    $finalTokens[$newStackPtr] = [
2✔
837
                        'code'    => T_OPEN_TAG,
2✔
838
                        'type'    => 'T_OPEN_TAG',
2✔
839
                        'content' => $token[1].$tokens[($stackPtr + 1)][1],
2✔
840
                    ];
841

842
                    $stackPtr++;
2✔
843
                }
844

845
                $newStackPtr++;
4✔
846
                continue;
4✔
847
            }//end if
848

849
            /*
850
                Parse doc blocks into something that can be easily iterated over.
851
            */
852

853
            if ($tokenIsArray === true
2,026✔
854
                && ($token[0] === T_DOC_COMMENT
3,001✔
855
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
3,001✔
856
            ) {
975✔
857
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
93✔
858
                foreach ($commentTokens as $commentToken) {
93✔
859
                    $finalTokens[$newStackPtr] = $commentToken;
93✔
860
                    $newStackPtr++;
93✔
861
                }
31✔
862

863
                continue;
93✔
864
            }
865

866
            /*
867
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
868
            */
869

870
            if (PHP_VERSION_ID >= 80000
3,001✔
871
                && $tokenIsArray === true
3,001✔
872
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
3,001✔
873
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
874
                && is_array($tokens[($stackPtr + 1)]) === true
3,001✔
875
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,001✔
876
            ) {
975✔
877
                $nextToken = $tokens[($stackPtr + 1)];
779✔
878

879
                // If the next token is a single new line, merge it into the comment token
880
                // and set to it up to be skipped.
881
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
779✔
882
                    $token[1] .= $nextToken[1];
562✔
883
                    $tokens[($stackPtr + 1)] = null;
562✔
884

885
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
562✔
886
                        echo "\t\t* merged newline after comment into comment token $stackPtr".PHP_EOL;
×
887
                    }
888
                } else {
889
                    // This may be a whitespace token consisting of multiple new lines.
890
                    if (strpos($nextToken[1], "\r\n") === 0) {
609✔
891
                        $token[1] .= "\r\n";
15✔
892
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
15✔
893
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
594✔
894
                        $token[1] .= "\n\r";
×
895
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
896
                    } else if (strpos($nextToken[1], "\n") === 0) {
594✔
897
                        $token[1] .= "\n";
594✔
898
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
594✔
899
                    }
900

901
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
609✔
902
                        echo "\t\t* stripped first newline after comment and added it to comment token $stackPtr".PHP_EOL;
×
903
                    }
904
                }//end if
905
            }//end if
906

907
            /*
908
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
909
                T_LNUMBER and T_STRING token values into a single token value, and
910
                then ignore the T_STRING token.
911
            */
912

913
            if (PHP_VERSION_ID < 80100
3,001✔
914
                && $tokenIsArray === true && $token[1] === '0'
3,001✔
915
                && (isset($tokens[($stackPtr + 1)]) === true
2,550✔
916
                && is_array($tokens[($stackPtr + 1)]) === true
2,099✔
917
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,099✔
918
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,099✔
919
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,099✔
920
                && $tokens[($stackPtr + 1)][1][1] !== '_')
2,550✔
921
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
3,001✔
922
            ) {
975✔
923
                $finalTokens[$newStackPtr] = [
74✔
924
                    'code'    => T_LNUMBER,
74✔
925
                    'type'    => 'T_LNUMBER',
74✔
926
                    'content' => $token[1] .= $matches[1],
74✔
927
                ];
928
                $newStackPtr++;
74✔
929

930
                if (isset($matches[2]) === true && $matches[2] !== '') {
74✔
931
                    $type = 'T_LNUMBER';
20✔
932
                    if ($matches[2][0] === '_') {
20✔
933
                        $type = 'T_STRING';
20✔
934
                    }
10✔
935

936
                    $finalTokens[$newStackPtr] = [
20✔
937
                        'code'    => constant($type),
20✔
938
                        'type'    => $type,
20✔
939
                        'content' => $matches[2],
20✔
940
                    ];
941
                    $newStackPtr++;
20✔
942
                }
10✔
943

944
                $stackPtr++;
74✔
945
                continue;
74✔
946
            }//end if
947

948
            /*
949
                PHP 8.1 introduced two dedicated tokens for the & character.
950
                Retokenizing both of these to T_BITWISE_AND, which is the
951
                token PHPCS already tokenized them as.
952
            */
953

954
            if ($tokenIsArray === true
2,026✔
955
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
3,001✔
956
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
3,001✔
957
            ) {
975✔
958
                $finalTokens[$newStackPtr] = [
435✔
959
                    'code'    => T_BITWISE_AND,
435✔
960
                    'type'    => 'T_BITWISE_AND',
435✔
961
                    'content' => $token[1],
435✔
962
                ];
435✔
963
                $newStackPtr++;
435✔
964
                continue;
435✔
965
            }
966

967
            /*
968
                If this is a double quoted string, PHP will tokenize the whole
969
                thing which causes problems with the scope map when braces are
970
                within the string. So we need to merge the tokens together to
971
                provide a single string.
972
            */
973

974
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
3,001✔
975
                // Binary casts need a special token.
976
                if ($token[0] === 'b"') {
105✔
977
                    $finalTokens[$newStackPtr] = [
×
978
                        'code'    => T_BINARY_CAST,
×
979
                        'type'    => 'T_BINARY_CAST',
×
980
                        'content' => 'b',
×
981
                    ];
982
                    $newStackPtr++;
×
983
                }
984

985
                $tokenContent = '"';
105✔
986
                $nestedVars   = [];
105✔
987
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
105✔
988
                    $subToken        = (array) $tokens[$i];
105✔
989
                    $subTokenIsArray = isset($subToken[1]);
105✔
990

991
                    if ($subTokenIsArray === true) {
105✔
992
                        $tokenContent .= $subToken[1];
105✔
993
                        if (($subToken[1] === '{'
105✔
994
                            || $subToken[1] === '${')
105✔
995
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
105✔
996
                        ) {
35✔
997
                            $nestedVars[] = $i;
75✔
998
                        }
20✔
999
                    } else {
35✔
1000
                        $tokenContent .= $subToken[0];
105✔
1001
                        if ($subToken[0] === '}') {
105✔
1002
                            array_pop($nestedVars);
60✔
1003
                        }
20✔
1004
                    }
1005

1006
                    if ($subTokenIsArray === false
70✔
1007
                        && $subToken[0] === '"'
105✔
1008
                        && empty($nestedVars) === true
105✔
1009
                    ) {
35✔
1010
                        // We found the other end of the double quoted string.
1011
                        break;
105✔
1012
                    }
1013
                }//end for
35✔
1014

1015
                $stackPtr = $i;
105✔
1016

1017
                // Convert each line within the double quoted string to a
1018
                // new token, so it conforms with other multiple line tokens.
1019
                $tokenLines = explode($this->eolChar, $tokenContent);
105✔
1020
                $numLines   = count($tokenLines);
105✔
1021
                $newToken   = [];
105✔
1022

1023
                for ($j = 0; $j < $numLines; $j++) {
105✔
1024
                    $newToken['content'] = $tokenLines[$j];
105✔
1025
                    if ($j === ($numLines - 1)) {
105✔
1026
                        if ($tokenLines[$j] === '') {
105✔
1027
                            break;
75✔
1028
                        }
1029
                    } else {
35✔
1030
                        $newToken['content'] .= $this->eolChar;
60✔
1031
                    }
1032

1033
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
105✔
1034
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
105✔
1035
                    $finalTokens[$newStackPtr] = $newToken;
105✔
1036
                    $newStackPtr++;
105✔
1037
                }
35✔
1038

1039
                // Continue, as we're done with this token.
1040
                continue;
105✔
1041
            }//end if
1042

1043
            /*
1044
                Detect binary casting and assign the casts their own token.
1045
            */
1046

1047
            if ($tokenIsArray === true
2,026✔
1048
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
3,001✔
1049
                && (substr($token[1], 0, 2) === 'b"'
2,891✔
1050
                || substr($token[1], 0, 2) === "b'")
2,891✔
1051
            ) {
975✔
1052
                $finalTokens[$newStackPtr] = [
×
1053
                    'code'    => T_BINARY_CAST,
×
1054
                    'type'    => 'T_BINARY_CAST',
×
1055
                    'content' => 'b',
×
1056
                ];
1057
                $newStackPtr++;
×
1058
                $token[1] = substr($token[1], 1);
×
1059
            }
1060

1061
            if ($tokenIsArray === true
2,026✔
1062
                && $token[0] === T_STRING_CAST
3,001✔
1063
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
3,001✔
1064
            ) {
975✔
1065
                $finalTokens[$newStackPtr] = [
×
1066
                    'code'    => T_BINARY_CAST,
×
1067
                    'type'    => 'T_BINARY_CAST',
×
1068
                    'content' => $token[1],
×
1069
                ];
1070
                $newStackPtr++;
×
1071
                continue;
×
1072
            }
1073

1074
            /*
1075
                If this is a heredoc, PHP will tokenize the whole
1076
                thing which causes problems when heredocs don't
1077
                contain real PHP code, which is almost never.
1078
                We want to leave the start and end heredoc tokens
1079
                alone though.
1080
            */
1081

1082
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
3,001✔
1083
                // Add the start heredoc token to the final array.
1084
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
135✔
1085

1086
                // Check if this is actually a nowdoc and use a different token
1087
                // to help the sniffs.
1088
                $nowdoc = false;
135✔
1089
                if (strpos($token[1], "'") !== false) {
135✔
1090
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
18✔
1091
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
18✔
1092
                    $nowdoc = true;
18✔
1093
                }
6✔
1094

1095
                $tokenContent = '';
135✔
1096
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
135✔
1097
                    $subTokenIsArray = is_array($tokens[$i]);
135✔
1098
                    if ($subTokenIsArray === true
90✔
1099
                        && $tokens[$i][0] === T_END_HEREDOC
135✔
1100
                    ) {
45✔
1101
                        // We found the other end of the heredoc.
1102
                        break;
132✔
1103
                    }
1104

1105
                    if ($subTokenIsArray === true) {
135✔
1106
                        $tokenContent .= $tokens[$i][1];
135✔
1107
                    } else {
45✔
1108
                        $tokenContent .= $tokens[$i];
114✔
1109
                    }
1110
                }
45✔
1111

1112
                if ($i === $numTokens) {
135✔
1113
                    // We got to the end of the file and never
1114
                    // found the closing token, so this probably wasn't
1115
                    // a heredoc.
1116
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
1117
                        $type = $finalTokens[$newStackPtr]['type'];
×
1118
                        echo "\t\t* failed to find the end of the here/nowdoc".PHP_EOL;
×
1119
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
1120
                    }
1121

1122
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
3✔
1123
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
3✔
1124
                    $newStackPtr++;
3✔
1125
                    continue;
3✔
1126
                }
1127

1128
                $stackPtr = $i;
132✔
1129
                $newStackPtr++;
132✔
1130

1131
                // Convert each line within the heredoc to a
1132
                // new token, so it conforms with other multiple line tokens.
1133
                $tokenLines = explode($this->eolChar, $tokenContent);
132✔
1134
                $numLines   = count($tokenLines);
132✔
1135
                $newToken   = [];
132✔
1136

1137
                for ($j = 0; $j < $numLines; $j++) {
132✔
1138
                    $newToken['content'] = $tokenLines[$j];
132✔
1139
                    if ($j === ($numLines - 1)) {
132✔
1140
                        if ($tokenLines[$j] === '') {
132✔
1141
                            break;
132✔
1142
                        }
1143
                    } else {
1144
                        $newToken['content'] .= $this->eolChar;
132✔
1145
                    }
1146

1147
                    if ($nowdoc === true) {
132✔
1148
                        $newToken['code'] = T_NOWDOC;
18✔
1149
                        $newToken['type'] = 'T_NOWDOC';
18✔
1150
                    } else {
6✔
1151
                        $newToken['code'] = T_HEREDOC;
132✔
1152
                        $newToken['type'] = 'T_HEREDOC';
132✔
1153
                    }
1154

1155
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1156
                    $newStackPtr++;
132✔
1157
                }//end for
44✔
1158

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

1162
                if ($nowdoc === true) {
132✔
1163
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
18✔
1164
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
18✔
1165
                }
6✔
1166

1167
                $newStackPtr++;
132✔
1168

1169
                // Continue, as we're done with this token.
1170
                continue;
132✔
1171
            }//end if
1172

1173
            /*
1174
                Enum keyword for PHP < 8.1
1175
            */
1176

1177
            if ($tokenIsArray === true
2,026✔
1178
                && $token[0] === T_STRING
3,001✔
1179
                && strtolower($token[1]) === 'enum'
3,001✔
1180
            ) {
975✔
1181
                // Get the next non-empty token.
1182
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,405✔
1183
                    if (is_array($tokens[$i]) === false
1,405✔
1184
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,405✔
1185
                    ) {
469✔
1186
                        break;
1,405✔
1187
                    }
1188
                }
256✔
1189

1190
                if (isset($tokens[$i]) === true
1,405✔
1191
                    && is_array($tokens[$i]) === true
1,405✔
1192
                    && $tokens[$i][0] === T_STRING
1,405✔
1193
                ) {
469✔
1194
                    // Modify $tokens directly so we can use it later when converting enum "case".
1195
                    $tokens[$stackPtr][0] = T_ENUM;
550✔
1196

1197
                    $newToken            = [];
550✔
1198
                    $newToken['code']    = T_ENUM;
550✔
1199
                    $newToken['type']    = 'T_ENUM';
550✔
1200
                    $newToken['content'] = $token[1];
550✔
1201
                    $finalTokens[$newStackPtr] = $newToken;
550✔
1202

1203
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
550✔
1204
                        echo "\t\t* token $stackPtr changed from T_STRING to T_ENUM".PHP_EOL;
×
1205
                    }
1206

1207
                    $newStackPtr++;
550✔
1208
                    continue;
550✔
1209
                }
1210
            }//end if
429✔
1211

1212
            /*
1213
                Convert enum "case" to T_ENUM_CASE
1214
            */
1215

1216
            if ($tokenIsArray === true
2,026✔
1217
                && $token[0] === T_CASE
3,001✔
1218
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,001✔
1219
            ) {
975✔
1220
                $isEnumCase = false;
1,563✔
1221
                $scope      = 1;
1,563✔
1222

1223
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,563✔
1224
                    if ($tokens[$i] === '}') {
1,563✔
1225
                        $scope++;
1,518✔
1226
                        continue;
1,518✔
1227
                    }
1228

1229
                    if ($tokens[$i] === '{') {
1,563✔
1230
                        $scope--;
1,563✔
1231
                        continue;
1,563✔
1232
                    }
1233

1234
                    if (is_array($tokens[$i]) === false) {
1,563✔
1235
                        continue;
1,563✔
1236
                    }
1237

1238
                    if ($scope !== 0) {
1,563✔
1239
                        continue;
1,563✔
1240
                    }
1241

1242
                    if ($tokens[$i][0] === T_SWITCH) {
1,044✔
1243
                        break;
978✔
1244
                    }
1245

1246
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,044✔
1247
                        $isEnumCase = true;
129✔
1248
                        break;
129✔
1249
                    }
1250
                }//end for
348✔
1251

1252
                if ($isEnumCase === true) {
1,563✔
1253
                    // Modify $tokens directly so we can use it as optimisation for other enum "case".
1254
                    $tokens[$stackPtr][0] = T_ENUM_CASE;
129✔
1255

1256
                    $newToken            = [];
129✔
1257
                    $newToken['code']    = T_ENUM_CASE;
129✔
1258
                    $newToken['type']    = 'T_ENUM_CASE';
129✔
1259
                    $newToken['content'] = $token[1];
129✔
1260
                    $finalTokens[$newStackPtr] = $newToken;
129✔
1261

1262
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
129✔
1263
                        echo "\t\t* token $stackPtr changed from T_CASE to T_ENUM_CASE".PHP_EOL;
×
1264
                    }
1265

1266
                    $newStackPtr++;
129✔
1267
                    continue;
129✔
1268
                }
1269
            }//end if
499✔
1270

1271
            /*
1272
                Asymmetric visibility for PHP < 8.4
1273
            */
1274

1275
            if ($tokenIsArray === true
2,026✔
1276
                && in_array($token[0], [T_PUBLIC, T_PROTECTED, T_PRIVATE], true) === true
3,001✔
1277
                && ($stackPtr + 3) < $numTokens
3,001✔
1278
                && $tokens[($stackPtr + 1)] === '('
3,001✔
1279
                && is_array($tokens[($stackPtr + 2)]) === true
3,001✔
1280
                && $tokens[($stackPtr + 2)][0] === T_STRING
3,001✔
1281
                && strtolower($tokens[($stackPtr + 2)][1]) === 'set'
3,001✔
1282
                && $tokens[($stackPtr + 3)] === ')'
3,001✔
1283
            ) {
975✔
1284
                $newToken = [];
354✔
1285
                if ($token[0] === T_PUBLIC) {
354✔
1286
                    $oldCode          = 'T_PUBLIC';
346✔
1287
                    $newToken['code'] = T_PUBLIC_SET;
346✔
1288
                    $newToken['type'] = 'T_PUBLIC_SET';
346✔
1289
                } else if ($token[0] === T_PROTECTED) {
354✔
1290
                    $oldCode          = 'T_PROTECTED';
354✔
1291
                    $newToken['code'] = T_PROTECTED_SET;
354✔
1292
                    $newToken['type'] = 'T_PROTECTED_SET';
354✔
1293
                } else {
177✔
1294
                    $oldCode          = 'T_PRIVATE';
354✔
1295
                    $newToken['code'] = T_PRIVATE_SET;
354✔
1296
                    $newToken['type'] = 'T_PRIVATE_SET';
354✔
1297
                }
1298

1299
                $newToken['content']       = $token[1].'('.$tokens[($stackPtr + 2)][1].')';
354✔
1300
                $finalTokens[$newStackPtr] = $newToken;
354✔
1301
                $newStackPtr++;
354✔
1302

1303
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
354✔
1304
                    $newCode = $newToken['type'];
×
1305
                    echo "\t\t* tokens from $stackPtr changed from $oldCode to $newCode".PHP_EOL;
×
1306
                }
1307

1308
                // We processed an extra 3 tokens, for `(`, `set`, and `)`.
1309
                $stackPtr += 3;
354✔
1310

1311
                continue;
354✔
1312
            }//end if
1313

1314
            /*
1315
                As of PHP 8.0 fully qualified, partially qualified and namespace relative
1316
                identifier names are tokenized differently.
1317
                This "undoes" the new tokenization so the tokenization will be the same in
1318
                in PHP 5, 7 and 8.
1319
            */
1320

1321
            if (PHP_VERSION_ID >= 80000
3,001✔
1322
                && $tokenIsArray === true
3,001✔
1323
                && ($token[0] === T_NAME_QUALIFIED
1,988✔
1324
                || $token[0] === T_NAME_FULLY_QUALIFIED
1,013✔
1325
                || $token[0] === T_NAME_RELATIVE)
2,026✔
1326
            ) {
975✔
1327
                $name = $token[1];
625✔
1328

1329
                if ($token[0] === T_NAME_FULLY_QUALIFIED) {
625✔
1330
                    $newToken            = [];
354✔
1331
                    $newToken['code']    = T_NS_SEPARATOR;
354✔
1332
                    $newToken['type']    = 'T_NS_SEPARATOR';
354✔
1333
                    $newToken['content'] = '\\';
354✔
1334
                    $finalTokens[$newStackPtr] = $newToken;
354✔
1335
                    ++$newStackPtr;
354✔
1336

1337
                    $name = ltrim($name, '\\');
354✔
1338
                }
1339

1340
                if ($token[0] === T_NAME_RELATIVE) {
625✔
1341
                    $newToken            = [];
593✔
1342
                    $newToken['code']    = T_NAMESPACE;
593✔
1343
                    $newToken['type']    = 'T_NAMESPACE';
593✔
1344
                    $newToken['content'] = substr($name, 0, 9);
593✔
1345
                    $finalTokens[$newStackPtr] = $newToken;
593✔
1346
                    ++$newStackPtr;
593✔
1347

1348
                    $newToken            = [];
593✔
1349
                    $newToken['code']    = T_NS_SEPARATOR;
593✔
1350
                    $newToken['type']    = 'T_NS_SEPARATOR';
593✔
1351
                    $newToken['content'] = '\\';
593✔
1352
                    $finalTokens[$newStackPtr] = $newToken;
593✔
1353
                    ++$newStackPtr;
593✔
1354

1355
                    $name = substr($name, 10);
593✔
1356
                }
1357

1358
                $parts     = explode('\\', $name);
625✔
1359
                $partCount = count($parts);
625✔
1360
                $lastPart  = ($partCount - 1);
625✔
1361

1362
                foreach ($parts as $i => $part) {
625✔
1363
                    $newToken            = [];
625✔
1364
                    $newToken['code']    = T_STRING;
625✔
1365
                    $newToken['type']    = 'T_STRING';
625✔
1366
                    $newToken['content'] = $part;
625✔
1367
                    $finalTokens[$newStackPtr] = $newToken;
625✔
1368
                    ++$newStackPtr;
625✔
1369

1370
                    if ($i !== $lastPart) {
625✔
1371
                        $newToken            = [];
452✔
1372
                        $newToken['code']    = T_NS_SEPARATOR;
452✔
1373
                        $newToken['type']    = 'T_NS_SEPARATOR';
452✔
1374
                        $newToken['content'] = '\\';
452✔
1375
                        $finalTokens[$newStackPtr] = $newToken;
452✔
1376
                        ++$newStackPtr;
452✔
1377
                    }
1378
                }
1379

1380
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
625✔
1381
                    $type    = Tokens::tokenName($token[0]);
×
1382
                    $content = Common::prepareForOutput($token[1]);
×
1383
                    echo "\t\t* token $stackPtr split into individual tokens; was: $type => $content".PHP_EOL;
×
1384
                }
1385

1386
                continue;
625✔
1387
            }//end if
1388

1389
            /*
1390
                PHP 8.0 Attributes
1391
            */
1392

1393
            if (PHP_VERSION_ID < 80000
3,001✔
1394
                && $token[0] === T_COMMENT
3,001✔
1395
                && strpos($token[1], '#[') === 0
3,001✔
1396
            ) {
975✔
1397
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
34✔
1398
                if ($subTokens !== null) {
34✔
1399
                    array_splice($tokens, $stackPtr, 1, $subTokens);
34✔
1400
                    $numTokens = count($tokens);
34✔
1401

1402
                    $tokenIsArray = true;
34✔
1403
                    $token        = $tokens[$stackPtr];
34✔
1404
                } else {
17✔
1405
                    $token[0] = T_ATTRIBUTE;
34✔
1406
                }
1407
            }
17✔
1408

1409
            if ($tokenIsArray === true
2,026✔
1410
                && $token[0] === T_ATTRIBUTE
3,001✔
1411
            ) {
975✔
1412
                // Go looking for the close bracket.
1413
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1414

1415
                $newToken            = [];
51✔
1416
                $newToken['code']    = T_ATTRIBUTE;
51✔
1417
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1418
                $newToken['content'] = '#[';
51✔
1419
                $finalTokens[$newStackPtr] = $newToken;
51✔
1420

1421
                $tokens[$bracketCloser]    = [];
51✔
1422
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1423
                $tokens[$bracketCloser][1] = ']';
51✔
1424

1425
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1426
                    echo "\t\t* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END".PHP_EOL;
×
1427
                }
1428

1429
                $newStackPtr++;
51✔
1430
                continue;
51✔
1431
            }//end if
1432

1433
            /*
1434
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1435
                token and ensures that the colon after it is always T_COLON.
1436
            */
1437

1438
            if ($tokenIsArray === true
2,026✔
1439
                && ($token[0] === T_STRING
3,001✔
1440
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
3,001✔
1441
            ) {
975✔
1442
                // Get the next non-empty token.
1443
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,815✔
1444
                    if (is_array($tokens[$i]) === false
2,815✔
1445
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
2,815✔
1446
                    ) {
913✔
1447
                        break;
2,815✔
1448
                    }
1449
                }
875✔
1450

1451
                if (isset($tokens[$i]) === true
2,815✔
1452
                    && is_array($tokens[$i]) === false
2,815✔
1453
                    && $tokens[$i] === ':'
2,815✔
1454
                ) {
913✔
1455
                    // Get the previous non-empty token.
1456
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,635✔
1457
                        if (is_array($tokens[$j]) === false
1,635✔
1458
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
1,635✔
1459
                        ) {
545✔
1460
                            break;
1,635✔
1461
                        }
1462
                    }
538✔
1463

1464
                    if (is_array($tokens[$j]) === false
1,635✔
1465
                        && ($tokens[$j] === '('
1,585✔
1466
                        || $tokens[$j] === ',')
1,585✔
1467
                    ) {
545✔
1468
                        $newToken            = [];
690✔
1469
                        $newToken['code']    = T_PARAM_NAME;
690✔
1470
                        $newToken['type']    = 'T_PARAM_NAME';
690✔
1471
                        $newToken['content'] = $token[1];
690✔
1472
                        $finalTokens[$newStackPtr] = $newToken;
690✔
1473

1474
                        $newStackPtr++;
690✔
1475

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

1480
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
690✔
1481
                            $type = Tokens::tokenName($token[0]);
×
1482
                            echo "\t\t* token $stackPtr changed from $type to T_PARAM_NAME".PHP_EOL;
×
1483
                        }
1484

1485
                        continue;
690✔
1486
                    }
1487
                }//end if
528✔
1488
            }//end if
913✔
1489

1490
            /*
1491
                "readonly" keyword for PHP < 8.1
1492
            */
1493

1494
            if ($tokenIsArray === true
2,026✔
1495
                && strtolower($token[1]) === 'readonly'
3,001✔
1496
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,325✔
1497
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
2,363✔
1498
            ) {
975✔
1499
                // Get the next non-whitespace token.
1500
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,011✔
1501
                    if (is_array($tokens[$i]) === false
1,011✔
1502
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,011✔
1503
                    ) {
337✔
1504
                        break;
1,011✔
1505
                    }
1506
                }
337✔
1507

1508
                $isReadonlyKeyword = false;
1,011✔
1509

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

1522
                    $foundDNFParens = 1;
×
1523
                    $foundDNFPipe   = 0;
×
1524

1525
                    for (++$i; $i < $numTokens; $i++) {
×
1526
                        if (is_array($tokens[$i]) === true) {
×
1527
                            $tokenType = $tokens[$i][0];
×
1528
                        } else {
1529
                            $tokenType = $tokens[$i];
×
1530
                        }
1531

1532
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1533
                            continue;
×
1534
                        }
1535

1536
                        if ($tokenType === '|') {
×
1537
                            ++$foundDNFPipe;
×
1538
                            continue;
×
1539
                        }
1540

1541
                        if ($tokenType === ')') {
×
1542
                            ++$foundDNFParens;
×
1543
                            continue;
×
1544
                        }
1545

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

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

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

1573
                        break;
×
1574
                    }//end for
1575
                }//end if
1576

1577
                if ($isReadonlyKeyword === true) {
1,011✔
1578
                    $finalTokens[$newStackPtr] = [
1,011✔
1579
                        'code'    => T_READONLY,
1,011✔
1580
                        'type'    => 'T_READONLY',
1,011✔
1581
                        'content' => $token[1],
1,011✔
1582
                    ];
337✔
1583
                    $newStackPtr++;
1,011✔
1584

1585
                    // Also modify the original token stack so that
1586
                    // future checks (like looking for T_NULLABLE) can
1587
                    // detect the T_READONLY token more easily.
1588
                    $tokens[$stackPtr][0] = T_READONLY;
1,011✔
1589
                    $token[0] = T_READONLY;
1,011✔
1590

1591
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
1,011✔
1592
                        echo "\t\t* token $stackPtr changed from $type to T_READONLY".PHP_EOL;
337✔
1593
                    }
1594
                } else {
337✔
1595
                    $finalTokens[$newStackPtr] = [
×
1596
                        'code'    => T_STRING,
×
1597
                        'type'    => 'T_STRING',
×
1598
                        'content' => $token[1],
×
1599
                    ];
1600
                    $newStackPtr++;
×
1601

1602
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1603
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
1604
                    }
1605
                }//end if
1606

1607
                continue;
1,011✔
1608
            }//end if
1609

1610
            /*
1611
                Before PHP 7.0, "yield from" was tokenized as
1612
                T_YIELD, T_WHITESPACE and T_STRING. So look for
1613
                and change this token in earlier versions.
1614
            */
1615

1616
            if (PHP_VERSION_ID < 70000
3,001✔
1617
                && $tokenIsArray === true
3,001✔
1618
                && $token[0] === T_YIELD
3,001✔
1619
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1620
                && isset($tokens[($stackPtr + 2)]) === true
3,001✔
1621
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
3,001✔
1622
                && strpos($tokens[($stackPtr + 1)][1], $this->eolChar) === false
3,001✔
1623
                && $tokens[($stackPtr + 2)][0] === T_STRING
3,001✔
1624
                && strtolower($tokens[($stackPtr + 2)][1]) === 'from'
3,001✔
1625
            ) {
975✔
1626
                // Single-line "yield from" with only whitespace between.
1627
                $finalTokens[$newStackPtr] = [
193✔
1628
                    'code'    => T_YIELD_FROM,
193✔
1629
                    'type'    => 'T_YIELD_FROM',
193✔
1630
                    'content' => $token[1].$tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1],
193✔
1631
                ];
1632

1633
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
193✔
1634
                    for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) {
×
1635
                        $type    = Tokens::tokenName($tokens[$i][0]);
×
1636
                        $content = Common::prepareForOutput($tokens[$i][1]);
×
1637
                        echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL;
×
1638
                    }
1639
                }
1640

1641
                $newStackPtr++;
193✔
1642
                $stackPtr += 2;
193✔
1643

1644
                continue;
193✔
1645
            } else if (PHP_VERSION_ID < 80300
3,001✔
1646
                && $tokenIsArray === true
3,001✔
1647
                && $token[0] === T_STRING
3,001✔
1648
                && strtolower($token[1]) === 'from'
3,001✔
1649
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
3,001✔
1650
            ) {
975✔
1651
                /*
1652
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1653
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1654
                    We want to keep the tokenization of the tokens between, but need to change the
1655
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1656
                */
1657

1658
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
40✔
1659
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
40✔
1660

1661
                $finalTokens[$newStackPtr] = [
40✔
1662
                    'code'    => T_YIELD_FROM,
40✔
1663
                    'type'    => 'T_YIELD_FROM',
40✔
1664
                    'content' => $token[1],
40✔
1665
                ];
1666
                $newStackPtr++;
40✔
1667

1668
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
40✔
1669
                    echo "\t\t* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD".PHP_EOL;
×
1670
                    echo "\t\t* token $stackPtr changed into T_YIELD_FROM; was: T_STRING".PHP_EOL;
×
1671
                }
1672

1673
                continue;
40✔
1674
            } else if (PHP_VERSION_ID >= 70000
3,001✔
1675
                && $tokenIsArray === true
3,001✔
1676
                && $token[0] === T_YIELD_FROM
3,001✔
1677
                && strpos($token[1], $this->eolChar) !== false
3,001✔
1678
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
3,001✔
1679
            ) {
975✔
1680
                /*
1681
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1682
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1683
                    separately for consistency.
1684
                */
1685

1686
                $finalTokens[$newStackPtr] = [
40✔
1687
                    'code'    => T_YIELD_FROM,
40✔
1688
                    'type'    => 'T_YIELD_FROM',
40✔
1689
                    'content' => substr($token[1], 0, 5),
40✔
1690
                ];
20✔
1691
                $newStackPtr++;
40✔
1692

1693
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
40✔
1694
                $numLines   = count($tokenLines);
40✔
1695
                $newToken   = [
20✔
1696
                    'type'    => 'T_WHITESPACE',
40✔
1697
                    'code'    => T_WHITESPACE,
40✔
1698
                    'content' => '',
40✔
1699
                ];
20✔
1700

1701
                foreach ($tokenLines as $i => $line) {
40✔
1702
                    $newToken['content'] = $line;
40✔
1703
                    if ($i === ($numLines - 1)) {
40✔
1704
                        if ($line === '') {
40✔
1705
                            break;
20✔
1706
                        }
1707
                    } else {
1708
                        $newToken['content'] .= $this->eolChar;
40✔
1709
                    }
1710

1711
                    $finalTokens[$newStackPtr] = $newToken;
40✔
1712
                    $newStackPtr++;
40✔
1713
                }
1714

1715
                $finalTokens[$newStackPtr] = [
40✔
1716
                    'code'    => T_YIELD_FROM,
40✔
1717
                    'type'    => 'T_YIELD_FROM',
40✔
1718
                    'content' => substr($token[1], -4),
40✔
1719
                ];
20✔
1720
                $newStackPtr++;
40✔
1721

1722
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
40✔
1723
                    echo "\t\t* token $stackPtr split into 'yield', one or more whitespace tokens and 'from'".PHP_EOL;
×
1724
                }
1725

1726
                continue;
40✔
1727
            } else if (PHP_VERSION_ID >= 80300
3,001✔
1728
                && $tokenIsArray === true
3,001✔
1729
                && $token[0] === T_YIELD_FROM
3,001✔
1730
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
3,001✔
1731
                && stripos($token[1], 'yield') === 0
3,001✔
1732
            ) {
975✔
1733
                /*
1734
                    Since PHP 8.3, "yield from" allows for comments and will
1735
                    swallow the comment in the `T_YIELD_FROM` token.
1736
                    We need to split this up to allow for sniffs handling comments.
1737
                */
1738

1739
                $finalTokens[$newStackPtr] = [
20✔
1740
                    'code'    => T_YIELD_FROM,
20✔
1741
                    'type'    => 'T_YIELD_FROM',
20✔
1742
                    'content' => substr($token[1], 0, 5),
20✔
1743
                ];
20✔
1744
                $newStackPtr++;
20✔
1745

1746
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
20✔
1747
                // Remove the PHP open tag token.
1748
                array_shift($yieldFromSubtokens);
20✔
1749
                // Add the "from" keyword.
1750
                $yieldFromSubtokens[] = [
20✔
1751
                    0 => T_YIELD_FROM,
20✔
1752
                    1 => substr($token[1], -4),
20✔
1753
                ];
20✔
1754

1755
                // Inject the new tokens into the token stack.
1756
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
20✔
1757
                $numTokens = count($tokens);
20✔
1758

1759
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
20✔
1760
                    echo "\t\t* token $stackPtr split into parts (yield from with comment)".PHP_EOL;
×
1761
                }
1762

1763
                unset($yieldFromSubtokens);
20✔
1764
                continue;
20✔
1765
            }//end if
1766

1767
            /*
1768
                Before PHP 5.6, the ... operator was tokenized as three
1769
                T_STRING_CONCAT tokens in a row. So look for and combine
1770
                these tokens in earlier versions.
1771
            */
1772

1773
            if ($tokenIsArray === false
2,026✔
1774
                && $token[0] === '.'
3,001✔
1775
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1776
                && isset($tokens[($stackPtr + 2)]) === true
3,001✔
1777
                && $tokens[($stackPtr + 1)] === '.'
3,001✔
1778
                && $tokens[($stackPtr + 2)] === '.'
3,001✔
1779
            ) {
975✔
1780
                $newToken            = [];
213✔
1781
                $newToken['code']    = T_ELLIPSIS;
213✔
1782
                $newToken['type']    = 'T_ELLIPSIS';
213✔
1783
                $newToken['content'] = '...';
213✔
1784
                $finalTokens[$newStackPtr] = $newToken;
213✔
1785

1786
                $newStackPtr++;
213✔
1787
                $stackPtr += 2;
213✔
1788
                continue;
213✔
1789
            }
1790

1791
            /*
1792
                Before PHP 5.6, the ** operator was tokenized as two
1793
                T_MULTIPLY tokens in a row. So look for and combine
1794
                these tokens in earlier versions.
1795
            */
1796

1797
            if ($tokenIsArray === false
2,026✔
1798
                && $token[0] === '*'
3,001✔
1799
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1800
                && $tokens[($stackPtr + 1)] === '*'
3,001✔
1801
            ) {
975✔
1802
                $newToken            = [];
×
1803
                $newToken['code']    = T_POW;
×
1804
                $newToken['type']    = 'T_POW';
×
1805
                $newToken['content'] = '**';
×
1806
                $finalTokens[$newStackPtr] = $newToken;
×
1807

1808
                $newStackPtr++;
×
1809
                $stackPtr++;
×
1810
                continue;
×
1811
            }
1812

1813
            /*
1814
                Before PHP 5.6, the **= operator was tokenized as
1815
                T_MULTIPLY followed by T_MUL_EQUAL. So look for and combine
1816
                these tokens in earlier versions.
1817
            */
1818

1819
            if ($tokenIsArray === false
2,026✔
1820
                && $token[0] === '*'
3,001✔
1821
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1822
                && is_array($tokens[($stackPtr + 1)]) === true
3,001✔
1823
                && $tokens[($stackPtr + 1)][1] === '*='
3,001✔
1824
            ) {
975✔
1825
                $newToken            = [];
×
1826
                $newToken['code']    = T_POW_EQUAL;
×
1827
                $newToken['type']    = 'T_POW_EQUAL';
×
1828
                $newToken['content'] = '**=';
×
1829
                $finalTokens[$newStackPtr] = $newToken;
×
1830

1831
                $newStackPtr++;
×
1832
                $stackPtr++;
×
1833
                continue;
×
1834
            }
1835

1836
            /*
1837
                Before PHP 7, the ??= operator was tokenized as
1838
                T_INLINE_THEN, T_INLINE_THEN, T_EQUAL.
1839
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1840
                T_COALESCE, T_EQUAL.
1841
                So look for and combine these tokens in earlier versions.
1842
            */
1843

1844
            if (($tokenIsArray === false
2,026✔
1845
                && $token[0] === '?'
3,001✔
1846
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1847
                && $tokens[($stackPtr + 1)][0] === '?'
3,001✔
1848
                && isset($tokens[($stackPtr + 2)]) === true
3,001✔
1849
                && $tokens[($stackPtr + 2)][0] === '=')
975✔
1850
                || ($tokenIsArray === true
2,026✔
1851
                && $token[0] === T_COALESCE
3,001✔
1852
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1853
                && $tokens[($stackPtr + 1)][0] === '=')
3,001✔
1854
            ) {
975✔
1855
                $newToken            = [];
×
1856
                $newToken['code']    = T_COALESCE_EQUAL;
×
1857
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1858
                $newToken['content'] = '??=';
×
1859
                $finalTokens[$newStackPtr] = $newToken;
×
1860

1861
                $newStackPtr++;
×
1862
                $stackPtr++;
×
1863

1864
                if ($tokenIsArray === false) {
×
1865
                    // Pre PHP 7.
1866
                    $stackPtr++;
×
1867
                }
1868

1869
                continue;
×
1870
            }
1871

1872
            /*
1873
                Before PHP 7, the ?? operator was tokenized as
1874
                T_INLINE_THEN followed by T_INLINE_THEN.
1875
                So look for and combine these tokens in earlier versions.
1876
            */
1877

1878
            if ($tokenIsArray === false
2,026✔
1879
                && $token[0] === '?'
3,001✔
1880
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1881
                && $tokens[($stackPtr + 1)][0] === '?'
3,001✔
1882
            ) {
975✔
1883
                $newToken            = [];
61✔
1884
                $newToken['code']    = T_COALESCE;
61✔
1885
                $newToken['type']    = 'T_COALESCE';
61✔
1886
                $newToken['content'] = '??';
61✔
1887
                $finalTokens[$newStackPtr] = $newToken;
61✔
1888

1889
                $newStackPtr++;
61✔
1890
                $stackPtr++;
61✔
1891
                continue;
61✔
1892
            }
1893

1894
            /*
1895
                Before PHP 8, the ?-> operator was tokenized as
1896
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1897
                So look for and combine these tokens in earlier versions.
1898
            */
1899

1900
            if ($tokenIsArray === false
2,026✔
1901
                && $token[0] === '?'
3,001✔
1902
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1903
                && is_array($tokens[($stackPtr + 1)]) === true
3,001✔
1904
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
3,001✔
1905
            ) {
975✔
1906
                $newToken            = [];
54✔
1907
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
54✔
1908
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
54✔
1909
                $newToken['content'] = '?->';
54✔
1910
                $finalTokens[$newStackPtr] = $newToken;
54✔
1911

1912
                $newStackPtr++;
54✔
1913
                $stackPtr++;
54✔
1914
                continue;
54✔
1915
            }
1916

1917
            /*
1918
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1919
                tokens split the token with a T_STRING. So look for
1920
                and change these tokens in earlier versions.
1921
            */
1922

1923
            if (PHP_VERSION_ID < 70400
3,001✔
1924
                && ($tokenIsArray === true
3,001✔
1925
                && ($token[0] === T_LNUMBER
3,001✔
1926
                || $token[0] === T_DNUMBER)
3,001✔
1927
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
1928
                && is_array($tokens[($stackPtr + 1)]) === true
3,001✔
1929
                && $tokens[($stackPtr + 1)][0] === T_STRING
3,001✔
1930
                && $tokens[($stackPtr + 1)][1][0] === '_')
3,001✔
1931
            ) {
975✔
1932
                $newContent = $token[1];
54✔
1933
                $newType    = $token[0];
54✔
1934
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
54✔
1935
                    if (is_array($tokens[$i]) === false) {
54✔
1936
                        break;
54✔
1937
                    }
1938

1939
                    if ($tokens[$i][0] === T_LNUMBER
54✔
1940
                        || $tokens[$i][0] === T_DNUMBER
54✔
1941
                    ) {
27✔
1942
                        $newContent .= $tokens[$i][1];
54✔
1943
                        continue;
54✔
1944
                    }
1945

1946
                    if ($tokens[$i][0] === T_STRING
54✔
1947
                        && $tokens[$i][1][0] === '_'
54✔
1948
                        && ((strpos($newContent, '0x') === 0
54✔
1949
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
54✔
1950
                        || (strpos($newContent, '0x') !== 0
54✔
1951
                        && substr($newContent, -1) !== '.'
54✔
1952
                        && substr(strtolower($newContent), -1) !== 'e'
54✔
1953
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
54✔
1954
                    ) {
27✔
1955
                        $newContent .= $tokens[$i][1];
54✔
1956

1957
                        // Support floats.
1958
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
54✔
1959
                            && ($tokens[($i + 1)] === '-'
54✔
1960
                            || $tokens[($i + 1)] === '+')
54✔
1961
                        ) {
27✔
1962
                            $newContent .= $tokens[($i + 1)];
54✔
1963
                            $i++;
54✔
1964
                        }
27✔
1965

1966
                        continue;
54✔
1967
                    }//end if
1968

1969
                    break;
54✔
1970
                }//end for
1971

1972
                if ($newType === T_LNUMBER
27✔
1973
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1974
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1975
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1976
                    || (stripos($newContent, '0x') !== 0
54✔
1977
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
54✔
1978
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
54✔
1979
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1980
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
54✔
1981
                ) {
27✔
1982
                    $newType = T_DNUMBER;
54✔
1983
                }
27✔
1984

1985
                $newToken            = [];
54✔
1986
                $newToken['code']    = $newType;
54✔
1987
                $newToken['type']    = Tokens::tokenName($newType);
54✔
1988
                $newToken['content'] = $newContent;
54✔
1989
                $finalTokens[$newStackPtr] = $newToken;
54✔
1990

1991
                $newStackPtr++;
54✔
1992
                $stackPtr = ($i - 1);
54✔
1993
                continue;
54✔
1994
            }//end if
1995

1996
            /*
1997
                Backfill the T_MATCH token for PHP versions < 8.0 and
1998
                do initial correction for non-match expression T_MATCH tokens
1999
                to T_STRING for PHP >= 8.0.
2000
                A final check for non-match expression T_MATCH tokens is done
2001
                in PHP::processAdditional().
2002
            */
2003

2004
            if ($tokenIsArray === true
2,026✔
2005
                && (($token[0] === T_STRING
3,001✔
2006
                && strtolower($token[1]) === 'match')
2,888✔
2007
                || $token[0] === T_MATCH)
3,001✔
2008
            ) {
975✔
2009
                $isMatch = false;
750✔
2010
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
750✔
2011
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
750✔
2012
                        continue;
750✔
2013
                    }
2014

2015
                    if ($tokens[$x] !== '(') {
750✔
2016
                        // This is not a match expression.
2017
                        break;
529✔
2018
                    }
2019

2020
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
750✔
2021
                        // Also not a match expression.
2022
                        break;
183✔
2023
                    }
2024

2025
                    $isMatch = true;
750✔
2026
                    break;
750✔
2027
                }//end for
2028

2029
                if ($isMatch === true && $token[0] === T_STRING) {
750✔
2030
                    $newToken            = [];
500✔
2031
                    $newToken['code']    = T_MATCH;
500✔
2032
                    $newToken['type']    = 'T_MATCH';
500✔
2033
                    $newToken['content'] = $token[1];
500✔
2034

2035
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
500✔
2036
                        echo "\t\t* token $stackPtr changed from T_STRING to T_MATCH".PHP_EOL;
×
2037
                    }
2038

2039
                    $finalTokens[$newStackPtr] = $newToken;
500✔
2040
                    $newStackPtr++;
500✔
2041
                    continue;
500✔
2042
                } else if ($isMatch === false && $token[0] === T_MATCH) {
718✔
2043
                    // PHP 8.0, match keyword, but not a match expression.
2044
                    $newToken            = [];
61✔
2045
                    $newToken['code']    = T_STRING;
61✔
2046
                    $newToken['type']    = 'T_STRING';
61✔
2047
                    $newToken['content'] = $token[1];
61✔
2048

2049
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
61✔
2050
                        echo "\t\t* token $stackPtr changed from T_MATCH to T_STRING".PHP_EOL;
×
2051
                    }
2052

2053
                    $finalTokens[$newStackPtr] = $newToken;
61✔
2054
                    $newStackPtr++;
61✔
2055
                    continue;
61✔
2056
                }//end if
2057
            }//end if
234✔
2058

2059
            /*
2060
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
2061
                to prevent scope being set and the scope for switch default statements
2062
                breaking.
2063
            */
2064

2065
            if ($tokenIsArray === true
2,026✔
2066
                && $token[0] === T_DEFAULT
3,001✔
2067
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
3,001✔
2068
            ) {
975✔
2069
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,434✔
2070
                    if ($tokens[$x] === ',') {
1,434✔
2071
                        // Skip over potential trailing comma (supported in PHP).
2072
                        continue;
231✔
2073
                    }
2074

2075
                    if (is_array($tokens[$x]) === false
1,434✔
2076
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
1,434✔
2077
                    ) {
478✔
2078
                        // Non-empty, non-comma content.
2079
                        break;
1,434✔
2080
                    }
2081
                }
250✔
2082

2083
                if (isset($tokens[$x]) === true
1,434✔
2084
                    && is_array($tokens[$x]) === true
1,434✔
2085
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,434✔
2086
                ) {
478✔
2087
                    // Modify the original token stack for the double arrow so that
2088
                    // future checks can disregard the double arrow token more easily.
2089
                    // For match expression "case" statements, this is handled
2090
                    // in PHP::processAdditional().
2091
                    $tokens[$x][0] = T_MATCH_ARROW;
750✔
2092
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
750✔
2093
                        echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
2094
                    }
2095

2096
                    $newToken            = [];
750✔
2097
                    $newToken['code']    = T_MATCH_DEFAULT;
750✔
2098
                    $newToken['type']    = 'T_MATCH_DEFAULT';
750✔
2099
                    $newToken['content'] = $token[1];
750✔
2100

2101
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
750✔
2102
                        echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
×
2103
                    }
2104

2105
                    $finalTokens[$newStackPtr] = $newToken;
750✔
2106
                    $newStackPtr++;
750✔
2107
                    continue;
750✔
2108
                }//end if
2109
            }//end if
478✔
2110

2111
            /*
2112
                Convert ? to T_NULLABLE OR T_INLINE_THEN
2113
            */
2114

2115
            if ($tokenIsArray === false && $token[0] === '?') {
3,001✔
2116
                $newToken            = [];
1,186✔
2117
                $newToken['content'] = '?';
1,186✔
2118

2119
                // For typed constants, we only need to check the token before the ? to be sure.
2120
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,186✔
2121
                    $newToken['code'] = T_NULLABLE;
151✔
2122
                    $newToken['type'] = 'T_NULLABLE';
151✔
2123

2124
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
151✔
2125
                        echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2126
                    }
2127

2128
                    $finalTokens[$newStackPtr] = $newToken;
151✔
2129
                    $newStackPtr++;
151✔
2130
                    continue;
151✔
2131
                }
2132

2133
                /*
2134
                 * Check if the next non-empty token is one of the tokens which can be used
2135
                 * in type declarations. If not, it's definitely a ternary.
2136
                 * At this point, the only token types which need to be taken into consideration
2137
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
2138
                 */
2139

2140
                $lastRelevantNonEmpty = null;
1,186✔
2141

2142
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,186✔
2143
                    if (is_array($tokens[$i]) === true) {
1,186✔
2144
                        $tokenType = $tokens[$i][0];
1,186✔
2145
                    } else {
370✔
2146
                        $tokenType = $tokens[$i];
953✔
2147
                    }
2148

2149
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
1,186✔
2150
                        continue;
1,186✔
2151
                    }
2152

2153
                    if ($tokenType === T_STRING
816✔
2154
                        || $tokenType === T_NAME_FULLY_QUALIFIED
1,186✔
2155
                        || $tokenType === T_NAME_RELATIVE
1,186✔
2156
                        || $tokenType === T_NAME_QUALIFIED
1,186✔
2157
                        || $tokenType === T_ARRAY
1,186✔
2158
                        || $tokenType === T_NAMESPACE
1,186✔
2159
                        || $tokenType === T_NS_SEPARATOR
1,186✔
2160
                    ) {
370✔
2161
                        $lastRelevantNonEmpty = $tokenType;
953✔
2162
                        continue;
953✔
2163
                    }
2164

2165
                    if (($tokenType !== T_CALLABLE
816✔
2166
                        && isset($lastRelevantNonEmpty) === false)
1,186✔
2167
                        || ($lastRelevantNonEmpty === T_ARRAY
615✔
2168
                        && $tokenType === '(')
953✔
2169
                        || (($lastRelevantNonEmpty === T_STRING
1,084✔
2170
                        || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED
746✔
2171
                        || $lastRelevantNonEmpty === T_NAME_RELATIVE
408✔
2172
                        || $lastRelevantNonEmpty === T_NAME_QUALIFIED)
746✔
2173
                        && ($tokenType === T_DOUBLE_COLON
1,084✔
2174
                        || $tokenType === '('
1,084✔
2175
                        || $tokenType === ':'))
1,154✔
2176
                    ) {
370✔
2177
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,039✔
2178
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2179
                        }
2180

2181
                        $newToken['code'] = T_INLINE_THEN;
1,039✔
2182
                        $newToken['type'] = 'T_INLINE_THEN';
1,039✔
2183

2184
                        $insideInlineIf[] = $stackPtr;
1,039✔
2185

2186
                        $finalTokens[$newStackPtr] = $newToken;
1,039✔
2187
                        $newStackPtr++;
1,039✔
2188
                        continue 2;
1,039✔
2189
                    }
2190

2191
                    break;
147✔
2192
                }//end for
2193

2194
                /*
2195
                 * This can still be a nullable type or a ternary.
2196
                 * Do additional checking.
2197
                 */
2198

2199
                $prevNonEmpty     = null;
168✔
2200
                $lastSeenNonEmpty = null;
168✔
2201

2202
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
168✔
2203
                    if (is_array($tokens[$i]) === true) {
168✔
2204
                        $tokenType = $tokens[$i][0];
168✔
2205
                    } else {
56✔
2206
                        $tokenType = $tokens[$i];
168✔
2207
                    }
2208

2209
                    if ($tokenType === T_STATIC
112✔
2210
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
112✔
2211
                        || $lastSeenNonEmpty === '(')
112✔
2212
                    ) {
56✔
2213
                        $lastSeenNonEmpty = $tokenType;
×
2214
                        continue;
×
2215
                    }
2216

2217
                    if ($prevNonEmpty === null
112✔
2218
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
168✔
2219
                    ) {
56✔
2220
                        // Found the previous non-empty token.
2221
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
168✔
2222
                            $newToken['code'] = T_NULLABLE;
135✔
2223
                            $newToken['type'] = 'T_NULLABLE';
135✔
2224

2225
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2226
                                echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2227
                            }
2228

2229
                            break;
135✔
2230
                        }
2231

2232
                        $prevNonEmpty = $tokenType;
168✔
2233
                    }
56✔
2234

2235
                    if ($tokenType === T_FUNCTION
112✔
2236
                        || $tokenType === T_FN
168✔
2237
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
168✔
2238
                        || isset(Tokens::$scopeModifiers[$tokenType]) === true
168✔
2239
                        || $tokenType === T_VAR
168✔
2240
                        || $tokenType === T_READONLY
168✔
2241
                    ) {
56✔
2242
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
147✔
2243
                            echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2244
                        }
2245

2246
                        $newToken['code'] = T_NULLABLE;
147✔
2247
                        $newToken['type'] = 'T_NULLABLE';
147✔
2248
                        break;
147✔
2249
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
168✔
2250
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
33✔
2251
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2252
                        }
2253

2254
                        $newToken['code'] = T_INLINE_THEN;
33✔
2255
                        $newToken['type'] = 'T_INLINE_THEN';
33✔
2256

2257
                        $insideInlineIf[] = $stackPtr;
33✔
2258
                        break;
33✔
2259
                    }
2260

2261
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
168✔
2262
                        $lastSeenNonEmpty = $tokenType;
168✔
2263
                    }
56✔
2264
                }//end for
56✔
2265

2266
                $finalTokens[$newStackPtr] = $newToken;
168✔
2267
                $newStackPtr++;
168✔
2268
                continue;
168✔
2269
            }//end if
2270

2271
            /*
2272
                Tokens after a double colon may look like scope openers,
2273
                such as when writing code like Foo::NAMESPACE, but they are
2274
                only ever variables or strings.
2275
            */
2276

2277
            if ($stackPtr > 1
2,026✔
2278
                && (is_array($tokens[($stackPtr - 1)]) === true
3,001✔
2279
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
3,001✔
2280
                && $tokenIsArray === true
3,001✔
2281
                && $token[0] !== T_STRING
3,001✔
2282
                && $token[0] !== T_VARIABLE
3,001✔
2283
                && $token[0] !== T_DOLLAR
3,001✔
2284
                && isset(Tokens::$emptyTokens[$token[0]]) === false
3,001✔
2285
            ) {
975✔
2286
                $newToken            = [];
×
2287
                $newToken['code']    = T_STRING;
×
2288
                $newToken['type']    = 'T_STRING';
×
2289
                $newToken['content'] = $token[1];
×
2290
                $finalTokens[$newStackPtr] = $newToken;
×
2291

2292
                $newStackPtr++;
×
2293
                continue;
×
2294
            }
2295

2296
            /*
2297
                Backfill the T_FN token for PHP versions < 7.4.
2298
            */
2299

2300
            if ($tokenIsArray === true
2,026✔
2301
                && $token[0] === T_STRING
3,001✔
2302
                && strtolower($token[1]) === 'fn'
3,001✔
2303
            ) {
975✔
2304
                // Modify the original token stack so that
2305
                // future checks (like looking for T_NULLABLE) can
2306
                // detect the T_FN token more easily.
2307
                $tokens[$stackPtr][0] = T_FN;
1,286✔
2308
                $token[0] = T_FN;
1,286✔
2309
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,286✔
2310
                    echo "\t\t* token $stackPtr changed from T_STRING to T_FN".PHP_EOL;
×
2311
                }
2312
            }
643✔
2313

2314
            /*
2315
                This is a special condition for T_ARRAY tokens used for
2316
                function return types. We want to keep the parenthesis map clean,
2317
                so let's tag these tokens as T_STRING.
2318
            */
2319

2320
            if ($tokenIsArray === true
2,026✔
2321
                && ($token[0] === T_FUNCTION
3,001✔
2322
                || $token[0] === T_FN)
3,001✔
2323
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
3,001✔
2324
            ) {
975✔
2325
                // Go looking for the colon to start the return type hint.
2326
                // Start by finding the closing parenthesis of the function.
2327
                $parenthesisStack  = [];
2,364✔
2328
                $parenthesisCloser = false;
2,364✔
2329
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,364✔
2330
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,364✔
2331
                        $parenthesisStack[] = $x;
2,364✔
2332
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,364✔
2333
                        array_pop($parenthesisStack);
2,364✔
2334
                        if (empty($parenthesisStack) === true) {
2,364✔
2335
                            $parenthesisCloser = $x;
2,364✔
2336
                            break;
2,364✔
2337
                        }
2338
                    }
196✔
2339
                }
788✔
2340

2341
                if ($parenthesisCloser !== false) {
2,364✔
2342
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,364✔
2343
                        if (is_array($tokens[$x]) === false
2,364✔
2344
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
2,364✔
2345
                        ) {
788✔
2346
                            // Non-empty content.
2347
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,364✔
2348
                                // Found a use statement, so search ahead for the closing parenthesis.
2349
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
2350
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
2351
                                        continue(2);
57✔
2352
                                    }
2353
                                }
19✔
2354
                            }
2355

2356
                            break;
2,364✔
2357
                        }
2358
                    }
767✔
2359

2360
                    if (isset($tokens[$x]) === true
2,364✔
2361
                        && is_array($tokens[$x]) === false
2,364✔
2362
                        && $tokens[$x] === ':'
2,364✔
2363
                    ) {
788✔
2364
                        // Find the start of the return type.
2365
                        for ($x += 1; $x < $numTokens; $x++) {
1,959✔
2366
                            if (is_array($tokens[$x]) === true
1,959✔
2367
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
1,959✔
2368
                            ) {
653✔
2369
                                // Whitespace or comments before the return type.
2370
                                continue;
1,959✔
2371
                            }
2372

2373
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1,959✔
2374
                                // Found a nullable operator, so skip it.
2375
                                // But also convert the token to save the tokenizer
2376
                                // a bit of time later on.
2377
                                $tokens[$x] = [
135✔
2378
                                    T_NULLABLE,
135✔
2379
                                    '?',
135✔
2380
                                ];
45✔
2381

2382
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2383
                                    echo "\t\t* token $x changed from ? to T_NULLABLE".PHP_EOL;
×
2384
                                }
2385

2386
                                continue;
135✔
2387
                            }
2388

2389
                            break;
1,959✔
2390
                        }//end for
2391
                    }//end if
653✔
2392
                }//end if
788✔
2393
            }//end if
788✔
2394

2395
            /*
2396
                Before PHP 7, the <=> operator was tokenized as
2397
                T_IS_SMALLER_OR_EQUAL followed by T_GREATER_THAN.
2398
                So look for and combine these tokens in earlier versions.
2399
            */
2400

2401
            if ($tokenIsArray === true
2,026✔
2402
                && $token[0] === T_IS_SMALLER_OR_EQUAL
3,001✔
2403
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
2404
                && $tokens[($stackPtr + 1)][0] === '>'
3,001✔
2405
            ) {
975✔
2406
                $newToken            = [];
×
2407
                $newToken['code']    = T_SPACESHIP;
×
2408
                $newToken['type']    = 'T_SPACESHIP';
×
2409
                $newToken['content'] = '<=>';
×
2410
                $finalTokens[$newStackPtr] = $newToken;
×
2411

2412
                $newStackPtr++;
×
2413
                $stackPtr++;
×
2414
                continue;
×
2415
            }
2416

2417
            /*
2418
                PHP doesn't assign a token to goto labels, so we have to.
2419
                These are just string tokens with a single colon after them. Double
2420
                colons are already tokenized and so don't interfere with this check.
2421
                But we do have to account for CASE statements, that look just like
2422
                goto labels.
2423
            */
2424

2425
            if ($tokenIsArray === true
2,026✔
2426
                && $token[0] === T_STRING
3,001✔
2427
                && isset($tokens[($stackPtr + 1)]) === true
3,001✔
2428
                && $tokens[($stackPtr + 1)] === ':'
3,001✔
2429
                && (is_array($tokens[($stackPtr - 1)]) === false
2,448✔
2430
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
2,486✔
2431
            ) {
975✔
2432
                $stopTokens = [
460✔
2433
                    T_CASE               => true,
1,380✔
2434
                    T_SEMICOLON          => true,
1,380✔
2435
                    T_OPEN_TAG           => true,
1,380✔
2436
                    T_OPEN_CURLY_BRACKET => true,
1,380✔
2437
                    T_INLINE_THEN        => true,
1,380✔
2438
                    T_ENUM               => true,
1,380✔
2439
                ];
920✔
2440

2441
                for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,380✔
2442
                    if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,380✔
2443
                        break;
1,380✔
2444
                    }
2445
                }
460✔
2446

2447
                if ($finalTokens[$x]['code'] !== T_CASE
1,380✔
2448
                    && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,380✔
2449
                    && $finalTokens[$x]['code'] !== T_ENUM
1,380✔
2450
                ) {
460✔
2451
                    $finalTokens[$newStackPtr] = [
108✔
2452
                        'content' => $token[1].':',
108✔
2453
                        'code'    => T_GOTO_LABEL,
108✔
2454
                        'type'    => 'T_GOTO_LABEL',
108✔
2455
                    ];
36✔
2456

2457
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
108✔
2458
                        echo "\t\t* token $stackPtr changed from T_STRING to T_GOTO_LABEL".PHP_EOL;
×
2459
                        echo "\t\t* skipping T_COLON token ".($stackPtr + 1).PHP_EOL;
×
2460
                    }
2461

2462
                    $newStackPtr++;
108✔
2463
                    $stackPtr++;
108✔
2464
                    continue;
108✔
2465
                }
2466
            }//end if
460✔
2467

2468
            /*
2469
                If this token has newlines in its content, split each line up
2470
                and create a new token for each line. We do this so it's easier
2471
                to ascertain where errors occur on a line.
2472
                Note that $token[1] is the token's content.
2473
            */
2474

2475
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
3,001✔
2476
                $tokenLines = explode($this->eolChar, $token[1]);
3,001✔
2477
                $numLines   = count($tokenLines);
3,001✔
2478
                $newToken   = [
1,013✔
2479
                    'type'    => Tokens::tokenName($token[0]),
3,001✔
2480
                    'code'    => $token[0],
3,001✔
2481
                    'content' => '',
3,001✔
2482
                ];
1,988✔
2483

2484
                for ($i = 0; $i < $numLines; $i++) {
3,001✔
2485
                    $newToken['content'] = $tokenLines[$i];
3,001✔
2486
                    if ($i === ($numLines - 1)) {
3,001✔
2487
                        if ($tokenLines[$i] === '') {
3,001✔
2488
                            break;
3,001✔
2489
                        }
2490
                    } else {
897✔
2491
                        $newToken['content'] .= $this->eolChar;
3,001✔
2492
                    }
2493

2494
                    $finalTokens[$newStackPtr] = $newToken;
3,001✔
2495
                    $newStackPtr++;
3,001✔
2496
                }
975✔
2497
            } else {
975✔
2498
                // Some T_STRING tokens should remain that way due to their context.
2499
                if ($tokenIsArray === true && $token[0] === T_STRING) {
3,001✔
2500
                    $preserveTstring = false;
2,662✔
2501

2502
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2503
                    // but the constant name should not be.
2504
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,662✔
2505
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,586✔
2506
                        || $insideConstDeclaration === true
1,800✔
2507
                    ) {
862✔
2508
                        // Find the next non-empty token.
2509
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,659✔
2510
                            if (is_array($tokens[$i]) === true
1,659✔
2511
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,659✔
2512
                            ) {
552✔
2513
                                continue;
1,524✔
2514
                            }
2515

2516
                            break;
1,659✔
2517
                        }
2518

2519
                        if ($tokens[$i] === '=') {
1,659✔
2520
                            $preserveTstring        = true;
1,464✔
2521
                            $insideConstDeclaration = false;
1,529✔
2522
                        }
487✔
2523
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,662✔
2524
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,662✔
2525
                    ) {
862✔
2526
                        $preserveTstring = true;
2,548✔
2527

2528
                        // Special case for syntax like: return new self/new parent
2529
                        // where self/parent should not be a string.
2530
                        $tokenContentLower = strtolower($token[1]);
2,548✔
2531
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,548✔
2532
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,548✔
2533
                        ) {
824✔
2534
                            $preserveTstring = false;
1,608✔
2535
                        }
373✔
2536
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,648✔
2537
                        // Function names for functions declared to return by reference.
2538
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,056✔
2539
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,056✔
2540
                                continue;
480✔
2541
                            }
2542

2543
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,056✔
2544
                                $preserveTstring = true;
480✔
2545
                            }
160✔
2546

2547
                            break;
1,056✔
2548
                        }
2549
                    } else {
352✔
2550
                        // Keywords with special PHPCS token when used as a function call.
2551
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,641✔
2552
                            if (is_array($tokens[$i]) === true
2,641✔
2553
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,641✔
2554
                            ) {
855✔
2555
                                continue;
2,268✔
2556
                            }
2557

2558
                            if ($tokens[$i][0] === '(') {
2,641✔
2559
                                $preserveTstring = true;
2,229✔
2560
                            }
743✔
2561

2562
                            break;
2,641✔
2563
                        }
2564
                    }//end if
2565

2566
                    if ($preserveTstring === true) {
2,662✔
2567
                        $finalTokens[$newStackPtr] = [
2,551✔
2568
                            'code'    => T_STRING,
2,551✔
2569
                            'type'    => 'T_STRING',
2,551✔
2570
                            'content' => $token[1],
2,551✔
2571
                        ];
863✔
2572

2573
                        $newStackPtr++;
2,551✔
2574
                        continue;
2,551✔
2575
                    }
2576
                }//end if
854✔
2577

2578
                $newToken = null;
3,001✔
2579
                if ($tokenIsArray === false) {
3,001✔
2580
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
2,989✔
2581
                        $newToken = self::$resolveTokenCache[$token[0]];
2,989✔
2582
                    }
971✔
2583
                } else {
971✔
2584
                    $cacheKey = null;
3,001✔
2585
                    if ($token[0] === T_STRING) {
3,001✔
2586
                        $cacheKey = strtolower($token[1]);
2,638✔
2587
                    } else if ($token[0] !== T_CURLY_OPEN) {
3,001✔
2588
                        $cacheKey = $token[0];
3,001✔
2589
                    }
975✔
2590

2591
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3,001✔
2592
                        $newToken            = self::$resolveTokenCache[$cacheKey];
3,001✔
2593
                        $newToken['content'] = $token[1];
3,001✔
2594
                    }
975✔
2595
                }
2596

2597
                if ($newToken === null) {
3,001✔
2598
                    $newToken = self::standardiseToken($token);
57✔
2599
                }
19✔
2600

2601
                // Convert colons that are actually the ELSE component of an
2602
                // inline IF statement.
2603
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
3,001✔
2604
                    $isInlineIf = true;
1,039✔
2605

2606
                    // Make sure this isn't a named parameter label.
2607
                    // Get the previous non-empty token.
2608
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,039✔
2609
                        if (is_array($tokens[$i]) === false
1,039✔
2610
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,039✔
2611
                        ) {
321✔
2612
                            break;
1,039✔
2613
                        }
2614
                    }
321✔
2615

2616
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,039✔
2617
                        $isInlineIf = false;
639✔
2618
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2619
                            echo "\t\t* token is parameter label, not T_INLINE_ELSE".PHP_EOL;
×
2620
                        }
2621
                    }
213✔
2622

2623
                    if ($isInlineIf === true) {
1,039✔
2624
                        // Make sure this isn't a return type separator.
2625
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,039✔
2626
                            if (is_array($tokens[$i]) === false
1,039✔
2627
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,039✔
2628
                                && $tokens[$i][0] !== T_COMMENT
1,039✔
2629
                                && $tokens[$i][0] !== T_WHITESPACE)
1,039✔
2630
                            ) {
321✔
2631
                                break;
1,039✔
2632
                            }
2633
                        }
321✔
2634

2635
                        if ($tokens[$i] === ')') {
1,039✔
2636
                            $parenCount = 1;
639✔
2637
                            for ($i--; $i > 0; $i--) {
639✔
2638
                                if ($tokens[$i] === '(') {
639✔
2639
                                    $parenCount--;
639✔
2640
                                    if ($parenCount === 0) {
639✔
2641
                                        break;
639✔
2642
                                    }
2643
                                } else if ($tokens[$i] === ')') {
639✔
2644
                                    $parenCount++;
×
2645
                                }
2646
                            }
213✔
2647

2648
                            // We've found the open parenthesis, so if the previous
2649
                            // non-empty token is FUNCTION or USE, this is a return type.
2650
                            // Note that we need to skip T_STRING tokens here as these
2651
                            // can be function names.
2652
                            for ($i--; $i > 0; $i--) {
639✔
2653
                                if (is_array($tokens[$i]) === false
639✔
2654
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
639✔
2655
                                    && $tokens[$i][0] !== T_COMMENT
639✔
2656
                                    && $tokens[$i][0] !== T_WHITESPACE
639✔
2657
                                    && $tokens[$i][0] !== T_STRING)
639✔
2658
                                ) {
213✔
2659
                                    break;
639✔
2660
                                }
2661
                            }
213✔
2662

2663
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
639✔
2664
                                $isInlineIf = false;
639✔
2665
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2666
                                    echo "\t\t* token is return type, not T_INLINE_ELSE".PHP_EOL;
×
2667
                                }
2668
                            }
213✔
2669
                        }//end if
213✔
2670
                    }//end if
321✔
2671

2672
                    // Check to see if this is a CASE or DEFAULT opener.
2673
                    if ($isInlineIf === true) {
1,039✔
2674
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,039✔
2675
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,039✔
2676
                            if (is_array($tokens[$i]) === true
1,039✔
2677
                                && ($tokens[$i][0] === T_CASE
1,039✔
2678
                                || $tokens[$i][0] === T_DEFAULT)
1,039✔
2679
                            ) {
321✔
2680
                                $isInlineIf = false;
×
2681
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2682
                                    echo "\t\t* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE".PHP_EOL;
×
2683
                                }
2684

2685
                                break;
×
2686
                            }
2687

2688
                            if (is_array($tokens[$i]) === false
1,039✔
2689
                                && ($tokens[$i] === ';'
1,039✔
2690
                                || $tokens[$i] === '{'
1,039✔
2691
                                || $tokens[$i] === '}')
1,039✔
2692
                            ) {
321✔
2693
                                break;
822✔
2694
                            }
2695
                        }//end for
321✔
2696
                    }//end if
321✔
2697

2698
                    if ($isInlineIf === true) {
1,039✔
2699
                        array_pop($insideInlineIf);
1,039✔
2700
                        $newToken['code'] = T_INLINE_ELSE;
1,039✔
2701
                        $newToken['type'] = 'T_INLINE_ELSE';
1,039✔
2702

2703
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,039✔
2704
                            echo "\t\t* token changed from T_COLON to T_INLINE_ELSE".PHP_EOL;
×
2705
                        }
2706
                    }
321✔
2707
                }//end if
321✔
2708

2709
                // This is a special condition for T_ARRAY tokens used for anything else
2710
                // but array declarations, like type hinting function arguments as
2711
                // being arrays.
2712
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2713
                // T_STRING.
2714
                if ($newToken['code'] === T_ARRAY) {
3,001✔
2715
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,489✔
2716
                        if (is_array($tokens[$i]) === false
1,489✔
2717
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,489✔
2718
                        ) {
471✔
2719
                            // Non-empty content.
2720
                            break;
1,489✔
2721
                        }
2722
                    }
394✔
2723

2724
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,489✔
2725
                        $newToken['code'] = T_STRING;
1,258✔
2726
                        $newToken['type'] = 'T_STRING';
1,258✔
2727
                    }
394✔
2728
                }
471✔
2729

2730
                // This is a special case when checking PHP 5.5+ code in PHP < 5.5
2731
                // where "finally" should be T_FINALLY instead of T_STRING.
2732
                if ($newToken['code'] === T_STRING
3,001✔
2733
                    && strtolower($newToken['content']) === 'finally'
3,001✔
2734
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CLOSE_CURLY_BRACKET
3,001✔
2735
                ) {
975✔
2736
                    $newToken['code'] = T_FINALLY;
179✔
2737
                    $newToken['type'] = 'T_FINALLY';
179✔
2738
                }
179✔
2739

2740
                // This is a special case for PHP 5.6 use function and use const
2741
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2742
                // and T_CONST.
2743
                if (($newToken['code'] === T_FUNCTION
3,001✔
2744
                    || $newToken['code'] === T_CONST)
3,001✔
2745
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
3,001✔
2746
                ) {
975✔
2747
                    $newToken['code'] = T_STRING;
135✔
2748
                    $newToken['type'] = 'T_STRING';
135✔
2749
                }
45✔
2750

2751
                // This is a special case for use groups in PHP 7+ where leaving
2752
                // the curly braces as their normal tokens would confuse
2753
                // the scope map and sniffs.
2754
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
3,001✔
2755
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
3,001✔
2756
                ) {
975✔
2757
                    $newToken['code'] = T_OPEN_USE_GROUP;
135✔
2758
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
135✔
2759
                    $insideUseGroup   = true;
135✔
2760
                }
45✔
2761

2762
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
3,001✔
2763
                    $newToken['code'] = T_CLOSE_USE_GROUP;
135✔
2764
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
135✔
2765
                    $insideUseGroup   = false;
135✔
2766
                }
45✔
2767

2768
                $finalTokens[$newStackPtr] = $newToken;
3,001✔
2769
                $newStackPtr++;
3,001✔
2770
            }//end if
2771
        }//end for
975✔
2772

2773
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,001✔
2774
            echo "\t*** END PHP TOKENIZING ***".PHP_EOL;
×
2775
        }
2776

2777
        return $finalTokens;
3,001✔
2778

2779
    }//end tokenize()
2780

2781

2782
    /**
2783
     * Performs additional processing after main tokenizing.
2784
     *
2785
     * This additional processing checks for CASE statements that are using curly
2786
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2787
     * into T_CLOSURE when they are not standard function definitions. It also
2788
     * detects short array syntax and converts those square brackets into new tokens.
2789
     * It also corrects some usage of the static and class keywords. It also
2790
     * assigns tokens to function return types.
2791
     *
2792
     * @return void
2793
     */
2794
    protected function processAdditional()
1,819✔
2795
    {
2796
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,819✔
2797
            echo "\t*** START ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
2798
        }
2799

2800
        $this->createAttributesNestingMap();
1,819✔
2801

2802
        $numTokens         = count($this->tokens);
1,819✔
2803
        $lastSeenTypeToken = $numTokens;
1,819✔
2804

2805
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,819✔
2806
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2807
            if (isset($this->tokens[$i]['scope_opener']) === true
1,819✔
2808
                && isset($this->tokens[$i]['scope_condition']) === false
1,819✔
2809
            ) {
581✔
2810
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2811
            }
2812

2813
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,819✔
2814
                /*
2815
                    Detect functions that are actually closures and
2816
                    assign them a different token.
2817
                */
2818

2819
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,578✔
2820
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,566✔
2821
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,566✔
2822
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,566✔
2823
                        ) {
522✔
2824
                            break;
1,566✔
2825
                        }
2826
                    }
522✔
2827

2828
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,566✔
2829
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,089✔
2830
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,089✔
2831
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,089✔
2832
                            $line = $this->tokens[$i]['line'];
×
2833
                            echo "\t* token $i on line $line changed from T_FUNCTION to T_CLOSURE".PHP_EOL;
×
2834
                        }
2835

2836
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,089✔
2837
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
654✔
2838
                                continue;
×
2839
                            }
2840

2841
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
654✔
2842
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
654✔
2843
                                $type = $this->tokens[$x]['type'];
×
2844
                                echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2845
                            }
2846
                        }
218✔
2847
                    }
363✔
2848
                }//end if
522✔
2849

2850
                continue;
1,578✔
2851
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,819✔
2852
                /*
2853
                    Detect anonymous classes and assign them a different token.
2854
                */
2855

2856
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,582✔
2857
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,582✔
2858
                        break;
1,582✔
2859
                    }
2860
                }
502✔
2861

2862
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,582✔
2863
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,582✔
2864
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,564✔
2865
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,573✔
2866
                ) {
502✔
2867
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,054✔
2868
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,054✔
2869
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,054✔
2870
                        $line = $this->tokens[$i]['line'];
×
2871
                        echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL;
×
2872
                    }
2873

2874
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,054✔
2875
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,054✔
2876
                    ) {
326✔
2877
                        $closer = $this->tokens[$x]['parenthesis_closer'];
619✔
2878

2879
                        $this->tokens[$i]['parenthesis_opener']     = $x;
619✔
2880
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
619✔
2881
                        $this->tokens[$i]['parenthesis_owner']      = $i;
619✔
2882
                        $this->tokens[$x]['parenthesis_owner']      = $i;
619✔
2883
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
619✔
2884

2885
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
619✔
2886
                            $line = $this->tokens[$i]['line'];
×
2887
                            echo "\t\t* added parenthesis keys to T_ANON_CLASS token $i on line $line".PHP_EOL;
×
2888
                        }
2889
                    }
181✔
2890

2891
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,054✔
2892
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,054✔
2893
                            continue;
×
2894
                        }
2895

2896
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,054✔
2897
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,054✔
2898
                            $type = $this->tokens[$x]['type'];
×
2899
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2900
                        }
2901
                    }
326✔
2902
                }//end if
326✔
2903

2904
                continue;
1,582✔
2905
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,819✔
2906
                // Possible arrow function.
2907
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,173✔
2908
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,173✔
2909
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,173✔
2910
                    ) {
391✔
2911
                        // Non-whitespace content.
2912
                        break;
1,173✔
2913
                    }
2914
                }
357✔
2915

2916
                if (isset($this->tokens[$x]) === true
1,173✔
2917
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,173✔
2918
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,173✔
2919
                ) {
391✔
2920
                    $ignore  = Tokens::$emptyTokens;
1,170✔
2921
                    $ignore += [
390✔
2922
                        T_ARRAY                  => T_ARRAY,
1,170✔
2923
                        T_CALLABLE               => T_CALLABLE,
1,170✔
2924
                        T_COLON                  => T_COLON,
1,170✔
2925
                        T_NAMESPACE              => T_NAMESPACE,
1,170✔
2926
                        T_NS_SEPARATOR           => T_NS_SEPARATOR,
1,170✔
2927
                        T_NULL                   => T_NULL,
1,170✔
2928
                        T_TRUE                   => T_TRUE,
1,170✔
2929
                        T_FALSE                  => T_FALSE,
1,170✔
2930
                        T_NULLABLE               => T_NULLABLE,
1,170✔
2931
                        T_PARENT                 => T_PARENT,
1,170✔
2932
                        T_SELF                   => T_SELF,
1,170✔
2933
                        T_STATIC                 => T_STATIC,
1,170✔
2934
                        T_STRING                 => T_STRING,
1,170✔
2935
                        T_TYPE_UNION             => T_TYPE_UNION,
1,170✔
2936
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,170✔
2937
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,170✔
2938
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,170✔
2939
                    ];
390✔
2940

2941
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,170✔
2942
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,170✔
2943
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,170✔
2944
                            break;
1,170✔
2945
                        }
2946
                    }
390✔
2947

2948
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,170✔
2949
                        $endTokens = [
390✔
2950
                            T_COLON                => true,
1,170✔
2951
                            T_COMMA                => true,
1,170✔
2952
                            T_SEMICOLON            => true,
1,170✔
2953
                            T_CLOSE_PARENTHESIS    => true,
1,170✔
2954
                            T_CLOSE_SQUARE_BRACKET => true,
1,170✔
2955
                            T_CLOSE_CURLY_BRACKET  => true,
1,170✔
2956
                            T_CLOSE_SHORT_ARRAY    => true,
1,170✔
2957
                            T_OPEN_TAG             => true,
1,170✔
2958
                            T_CLOSE_TAG            => true,
1,170✔
2959
                        ];
780✔
2960

2961
                        $inTernary    = false;
1,170✔
2962
                        $lastEndToken = null;
1,170✔
2963

2964
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,170✔
2965
                            // Arrow function closer should never be shared with the closer of a match
2966
                            // control structure.
2967
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,170✔
2968
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,170✔
2969
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,170✔
2970
                            ) {
390✔
2971
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
423✔
2972
                                    // Match in return value of arrow function. Move on to the next token.
2973
                                    continue;
423✔
2974
                                }
2975

2976
                                // Arrow function as return value for the last match case without trailing comma.
2977
                                if ($lastEndToken !== null) {
423✔
2978
                                    $scopeCloser = $lastEndToken;
423✔
2979
                                    break;
423✔
2980
                                }
2981

2982
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2983
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2984
                                        $scopeCloser = $lastNonEmpty;
186✔
2985
                                        break 2;
186✔
2986
                                    }
2987
                                }
62✔
2988
                            }
2989

2990
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,170✔
2991
                                if ($lastEndToken !== null
780✔
2992
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
1,025✔
2993
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
697✔
2994
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
880✔
2995
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
1,025✔
2996
                                ) {
390✔
2997
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2998
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2999
                                            $scopeCloser = $lastNonEmpty;
186✔
3000
                                            break;
186✔
3001
                                        }
3002
                                    }
62✔
3003
                                }
62✔
3004

3005
                                break;
1,170✔
3006
                            }
3007

3008
                            if ($inTernary === false
780✔
3009
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,170✔
3010
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,170✔
3011
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,170✔
3012
                            ) {
390✔
3013
                                // Found a nested arrow function that already has the closer set and is in
3014
                                // the same scope as us, so we can use its closer.
3015
                                break;
186✔
3016
                            }
3017

3018
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,170✔
3019
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,170✔
3020
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,170✔
3021
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,170✔
3022
                            ) {
390✔
3023
                                // We minus 1 here in case the closer can be shared with us.
3024
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
423✔
3025
                                continue;
423✔
3026
                            }
3027

3028
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,170✔
3029
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
735✔
3030
                                $lastEndToken = $scopeCloser;
735✔
3031
                                continue;
735✔
3032
                            }
3033

3034
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,170✔
3035
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
186✔
3036
                                $lastEndToken = $scopeCloser;
186✔
3037
                                continue;
186✔
3038
                            }
3039

3040
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,170✔
3041
                                $inTernary = true;
186✔
3042
                                continue;
186✔
3043
                            }
3044

3045
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,170✔
3046
                                if ($inTernary === false) {
186✔
3047
                                    break;
186✔
3048
                                }
3049

3050
                                $inTernary = false;
186✔
3051
                                continue;
186✔
3052
                            }
3053
                        }//end for
390✔
3054

3055
                        if ($scopeCloser !== $numTokens) {
1,170✔
3056
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,170✔
3057
                                $line = $this->tokens[$i]['line'];
×
3058
                                echo "\t=> token $i on line $line processed as arrow function".PHP_EOL;
×
3059
                                echo "\t\t* scope opener set to $arrow *".PHP_EOL;
×
3060
                                echo "\t\t* scope closer set to $scopeCloser *".PHP_EOL;
×
3061
                                echo "\t\t* parenthesis opener set to $x *".PHP_EOL;
×
3062
                                echo "\t\t* parenthesis closer set to $closer *".PHP_EOL;
×
3063
                            }
3064

3065
                            $this->tokens[$i]['code']            = T_FN;
1,170✔
3066
                            $this->tokens[$i]['type']            = 'T_FN';
1,170✔
3067
                            $this->tokens[$i]['scope_condition'] = $i;
1,170✔
3068
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,170✔
3069
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,170✔
3070
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,170✔
3071
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,170✔
3072
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,170✔
3073

3074
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,170✔
3075
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,170✔
3076

3077
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,170✔
3078
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,170✔
3079
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,170✔
3080
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,170✔
3081
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,170✔
3082
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,170✔
3083

3084
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,170✔
3085
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,170✔
3086
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,170✔
3087
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,170✔
3088

3089
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,170✔
3090
                                $line = $this->tokens[$arrow]['line'];
×
3091
                                echo "\t\t* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW".PHP_EOL;
×
3092
                            }
3093
                        }//end if
390✔
3094
                    }//end if
390✔
3095
                }//end if
390✔
3096

3097
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
3098
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,173✔
3099
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
624✔
3100
                        $line = $this->tokens[$i]['line'];
×
3101
                        echo "\t=> token $i on line $line is not an arrow function".PHP_EOL;
×
3102
                        echo "\t\t* token changed from T_FN to T_STRING".PHP_EOL;
×
3103
                    }
3104

3105
                    $this->tokens[$i]['code'] = T_STRING;
624✔
3106
                    $this->tokens[$i]['type'] = 'T_STRING';
807✔
3107
                }
208✔
3108
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,819✔
3109
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,150✔
3110
                    continue;
99✔
3111
                }
3112

3113
                // Unless there is a variable or a bracket before this token,
3114
                // it is the start of an array being defined using the short syntax.
3115
                $isShortArray = false;
1,150✔
3116
                $allowed      = [
396✔
3117
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,150✔
3118
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,150✔
3119
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,150✔
3120
                    T_VARIABLE                 => T_VARIABLE,
1,150✔
3121
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,150✔
3122
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,150✔
3123
                    T_STRING                   => T_STRING,
1,150✔
3124
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,150✔
3125
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,150✔
3126
                ];
754✔
3127
                $allowed     += Tokens::$magicConstants;
1,150✔
3128

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

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

3148
                        break;
1,150✔
3149
                    }
3150
                }//end for
358✔
3151

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

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

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

3174
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3175
                        echo "\t\t* token $i changed from T_MATCH to T_STRING".PHP_EOL;
×
3176
                    }
3177

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

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

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

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

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

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

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

3231
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
477✔
3232
                                echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
3233
                            }
3234
                        }
159✔
3235
                    }//end for
159✔
3236
                }//end if
3237

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

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

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

3257
                $allowed = [
618✔
3258
                    T_STRING       => T_STRING,
1,816✔
3259
                    T_CALLABLE     => T_CALLABLE,
1,816✔
3260
                    T_SELF         => T_SELF,
1,816✔
3261
                    T_PARENT       => T_PARENT,
1,816✔
3262
                    T_STATIC       => T_STATIC,
1,816✔
3263
                    T_FALSE        => T_FALSE,
1,816✔
3264
                    T_TRUE         => T_TRUE,
1,816✔
3265
                    T_NULL         => T_NULL,
1,816✔
3266
                    T_NAMESPACE    => T_NAMESPACE,
1,816✔
3267
                    T_NS_SEPARATOR => T_NS_SEPARATOR,
1,816✔
3268
                ];
1,198✔
3269

3270
                $suspectedType       = null;
1,816✔
3271
                $typeTokenCountAfter = 0;
1,816✔
3272

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

3278
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,816✔
3279
                        ++$typeTokenCountAfter;
1,387✔
3280
                        continue;
1,387✔
3281
                    }
3282

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

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

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

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

3311
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,816✔
3312
                        && isset($this->tokens[$x]['scope_condition']) === true
1,816✔
3313
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,816✔
3314
                    ) {
580✔
3315
                        $suspectedType = 'return';
1,566✔
3316
                        break;
1,566✔
3317
                    }
3318

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

3325
                    break;
1,702✔
3326
                }//end for
3327

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

3337
                if ($suspectedType === 'property or parameter') {
1,777✔
3338
                    unset($allowed[T_STATIC]);
1,248✔
3339
                }
416✔
3340

3341
                $typeTokenCountBefore = 0;
1,777✔
3342
                $typeOperators        = [$i];
1,777✔
3343
                $parenthesesCount     = 0;
1,777✔
3344
                $confirmed            = false;
1,777✔
3345
                $maybeNullable        = null;
1,777✔
3346

3347
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,777✔
3348
                    ++$parenthesesCount;
1,777✔
3349
                }
567✔
3350

3351
                for ($x = ($i - 1); $x >= 0; $x--) {
1,777✔
3352
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,777✔
3353
                        continue;
1,489✔
3354
                    }
3355

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

3365
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
312✔
3366
                            $line = $this->tokens[$x]['line'];
×
3367
                            echo "\t* token $x on line $line changed back from T_STRING to T_STATIC".PHP_EOL;
×
3368
                        }
3369
                    }
104✔
3370

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

3392
                            if ($this->tokens[$y]['code'] === T_FN) {
1,248✔
3393
                                $confirmed = true;
933✔
3394
                                break;
933✔
3395
                            }
3396
                        }
3397
                    }//end if
416✔
3398

3399
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,777✔
3400
                        ++$typeTokenCountBefore;
1,489✔
3401
                        continue;
1,489✔
3402
                    }
3403

3404
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3405
                    if (($typeTokenCountBefore > 0
1,210✔
3406
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,777✔
3407
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,681✔
3408
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,681✔
3409
                    ) {
567✔
3410
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
886✔
3411
                            $maybeNullable = $x;
312✔
3412
                        }
104✔
3413

3414
                        continue;
886✔
3415
                    }
3416

3417
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,777✔
3418
                        $typeOperators[] = $x;
1,273✔
3419
                        continue;
1,273✔
3420
                    }
3421

3422
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,777✔
3423
                        ++$parenthesesCount;
1,489✔
3424
                        $typeOperators[] = $x;
1,489✔
3425
                        continue;
1,489✔
3426
                    }
3427

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

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

3441
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
969✔
3442
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
933✔
3443
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
519✔
3444
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
726✔
3445
                            ) {
311✔
3446
                                $confirmed = true;
933✔
3447
                            }
311✔
3448

3449
                            break;
933✔
3450
                        }
3451

3452
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3453
                        // Closure use tokens won't be parentheses owners until PHPCS 4.0.
3454
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
969✔
3455
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
933✔
3456
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
933✔
3457
                                    break;
933✔
3458
                                }
3459
                            }
249✔
3460

3461
                            if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
933✔
3462
                                $confirmed = true;
933✔
3463
                            }
311✔
3464
                        }
311✔
3465

3466
                        break;
969✔
3467
                    }//end if
3468

3469
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,777✔
3470
                        $confirmed = true;
922✔
3471
                        break;
922✔
3472
                    }
3473

3474
                    if ($suspectedType === 'property or parameter'
1,210✔
3475
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,526✔
3476
                        || $this->tokens[$x]['code'] === T_VAR
1,263✔
3477
                        || $this->tokens[$x]['code'] === T_STATIC
1,213✔
3478
                        || $this->tokens[$x]['code'] === T_READONLY
1,213✔
3479
                        || $this->tokens[$x]['code'] === T_FINAL)
1,514✔
3480
                    ) {
567✔
3481
                        // This will also confirm constructor property promotion parameters, but that's fine.
3482
                        $confirmed = true;
1,062✔
3483
                    }
354✔
3484

3485
                    break;
1,777✔
3486
                }//end for
3487

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

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

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

3511
                                break;
225✔
3512
                            }
3513

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

3522
                                    break;
×
3523
                                }
3524

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

3532
                    unset($parens, $last);
747✔
3533
                }//end if
249✔
3534

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

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

3545
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,237✔
3546
                            $line = $this->tokens[$x]['line'];
×
3547
                            echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
425✔
3548
                        }
3549
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,351✔
3550
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,237✔
3551
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,237✔
3552

3553
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,237✔
3554
                            $line = $this->tokens[$x]['line'];
×
3555
                            echo "\t* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION".PHP_EOL;
425✔
3556
                        }
3557
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,351✔
3558
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,351✔
3559
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,351✔
3560

3561
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,351✔
3562
                            $line = $this->tokens[$x]['line'];
×
3563
                            echo "\t* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS".PHP_EOL;
463✔
3564
                        }
3565
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,351✔
3566
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,351✔
3567
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,351✔
3568

3569
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,351✔
3570
                            $line = $this->tokens[$x]['line'];
×
3571
                            echo "\t* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS".PHP_EOL;
×
3572
                        }
3573
                    }//end if
425✔
3574
                }//end foreach
425✔
3575

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

3580
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
312✔
3581
                        $line = $this->tokens[$maybeNullable]['line'];
×
3582
                        echo "\t* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE".PHP_EOL;
×
3583
                    }
3584
                }
104✔
3585

3586
                continue;
1,351✔
3587
            } else if ($this->tokens[$i]['code'] === T_STATIC) {
1,819✔
3588
                for ($x = ($i - 1); $x > 0; $x--) {
1,342✔
3589
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,342✔
3590
                        break;
1,342✔
3591
                    }
3592
                }
422✔
3593

3594
                if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
1,342✔
3595
                    $this->tokens[$i]['code'] = T_STRING;
×
3596
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3597

3598
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3599
                        $line = $this->tokens[$i]['line'];
×
3600
                        echo "\t* token $i on line $line changed from T_STATIC to T_STRING".PHP_EOL;
×
3601
                    }
3602
                }
3603

3604
                continue;
1,342✔
3605
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,819✔
3606
                || $this->tokens[$i]['code'] === T_FALSE
1,819✔
3607
                || $this->tokens[$i]['code'] === T_NULL
1,819✔
3608
            ) {
581✔
3609
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,621✔
3610
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,621✔
3611
                        // Non-whitespace content.
3612
                        break;
1,621✔
3613
                    }
3614
                }
454✔
3615

3616
                if ($x !== $numTokens
1,106✔
3617
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,621✔
3618
                ) {
515✔
3619
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3620
                        $line = $this->tokens[$i]['line'];
×
3621
                        $type = $this->tokens[$i]['type'];
×
3622
                        echo "\t* token $i on line $line changed from $type to T_STRING".PHP_EOL;
×
3623
                    }
3624

3625
                    $this->tokens[$i]['code'] = T_STRING;
×
3626
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3627
                }
3628
            }//end if
515✔
3629

3630
            if (($this->tokens[$i]['code'] !== T_CASE
1,819✔
3631
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,819✔
3632
                || isset($this->tokens[$i]['scope_opener']) === false
1,394✔
3633
            ) {
581✔
3634
                // Only interested in CASE and DEFAULT statements from here on in.
3635
                continue;
1,819✔
3636
            }
3637

3638
            $scopeOpener = $this->tokens[$i]['scope_opener'];
468✔
3639
            $scopeCloser = $this->tokens[$i]['scope_closer'];
468✔
3640

3641
            // If the first char after the opener is a curly brace
3642
            // and that brace has been ignored, it is actually
3643
            // opening this case statement and the opener and closer are
3644
            // probably set incorrectly.
3645
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
468✔
3646
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
468✔
3647
                    // Non-whitespace content.
3648
                    break;
468✔
3649
                }
3650
            }
156✔
3651

3652
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
468✔
3653
                // Special case for multiple CASE statements that share the same
3654
                // closer. Because we are going backwards through the file, this next
3655
                // CASE statement is already fixed, so just use its closer and don't
3656
                // worry about fixing anything.
3657
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3658
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3659
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3660
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3661
                    $newType = $this->tokens[$newCloser]['type'];
×
3662
                    $line    = $this->tokens[$i]['line'];
×
3663
                    echo "\t* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3664
                }
3665

3666
                continue;
×
3667
            }
3668

3669
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
468✔
3670
                || isset($this->tokens[$x]['scope_condition']) === true
468✔
3671
            ) {
156✔
3672
                // Not a CASE/DEFAULT with a curly brace opener.
3673
                continue;
468✔
3674
            }
3675

3676
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3677
            // not whatever it already is. The opener needs to be the opening curly
3678
            // brace so everything matches up.
3679
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3680
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3681
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3682
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3683
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3684
            }
18✔
3685

3686
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3687
                $line      = $this->tokens[$i]['line'];
×
3688
                $tokenType = $this->tokens[$i]['type'];
×
3689

3690
                $oldType = $this->tokens[$scopeOpener]['type'];
×
3691
                $newType = $this->tokens[$x]['type'];
×
3692
                echo "\t* token $i ($tokenType) on line $line opener changed from $scopeOpener ($oldType) to $x ($newType)".PHP_EOL;
×
3693

3694
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3695
                $newType = $this->tokens[$newCloser]['type'];
×
3696
                echo "\t* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3697
            }
3698

3699
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3700
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3701
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3702
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3703
            }
18✔
3704

3705
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3706
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3707
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3708
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3709
            } else {
18✔
3710
                // We were using a shared closer. All tokens that were
3711
                // sharing this closer with us, except for the scope condition
3712
                // and it's opener, need to now point to the new closer.
3713
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3714
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3715
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3716
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3717
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3718
                    ) {
3719
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3720

3721
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3722
                            $line      = $this->tokens[$y]['line'];
×
3723
                            $tokenType = $this->tokens[$y]['type'];
×
3724
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3725
                            $newType   = $this->tokens[$newCloser]['type'];
×
3726
                            echo "\t\t* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3727
                        }
3728
                    }
3729
                }
3730
            }//end if
3731

3732
            unset($this->tokens[$x]['bracket_opener']);
54✔
3733
            unset($this->tokens[$x]['bracket_closer']);
54✔
3734
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3735
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3736
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3737

3738
            // Now fix up all the tokens that think they are
3739
            // inside the CASE/DEFAULT statement when they are really outside.
3740
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3741
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3742
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3743
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3744
                        unset($this->tokens[$x]['conditions'][$num]);
×
3745

3746
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3747
                            $type     = $this->tokens[$x]['type'];
×
3748
                            $oldConds = '';
×
3749
                            foreach ($oldConditions as $condition) {
×
3750
                                $oldConds .= Tokens::tokenName($condition).',';
×
3751
                            }
3752

3753
                            $oldConds = rtrim($oldConds, ',');
×
3754

3755
                            $newConds = '';
×
3756
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3757
                                $newConds .= Tokens::tokenName($condition).',';
×
3758
                            }
3759

3760
                            $newConds = rtrim($newConds, ',');
×
3761

3762
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
3763
                            echo "\t\t\t=> conditions changed from $oldConds to $newConds".PHP_EOL;
×
3764
                        }
3765

3766
                        break;
×
3767
                    }//end if
3768
                }//end foreach
3769
            }//end for
3770
        }//end for
18✔
3771

3772
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,819✔
3773
            echo "\t*** END ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
3774
        }
3775

3776
    }//end processAdditional()
1,200✔
3777

3778

3779
    /**
3780
     * Takes a token produced from <code>token_get_all()</code> and produces a
3781
     * more uniform token.
3782
     *
3783
     * @param string|array $token The token to convert.
3784
     *
3785
     * @return array The new token.
3786
     */
3787
    public static function standardiseToken($token)
3✔
3788
    {
3789
        if (isset($token[1]) === false) {
3✔
3790
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3✔
3791
                return self::$resolveTokenCache[$token[0]];
1✔
3792
            }
3793
        } else {
1✔
3794
            $cacheKey = null;
3✔
3795
            if ($token[0] === T_STRING) {
3✔
3796
                $cacheKey = strtolower($token[1]);
3✔
3797
            } else if ($token[0] !== T_CURLY_OPEN) {
3✔
3798
                $cacheKey = $token[0];
3✔
3799
            }
1✔
3800

3801
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3✔
3802
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3803
                $newToken['content'] = $token[1];
×
3804
                return $newToken;
×
3805
            }
3806
        }
3807

3808
        if (isset($token[1]) === false) {
3✔
3809
            return self::resolveSimpleToken($token[0]);
3✔
3810
        }
3811

3812
        if ($token[0] === T_STRING) {
3✔
3813
            switch ($cacheKey) {
1✔
3814
            case 'false':
3✔
3815
                $newToken['type'] = 'T_FALSE';
3✔
3816
                break;
3✔
3817
            case 'true':
3✔
3818
                $newToken['type'] = 'T_TRUE';
3✔
3819
                break;
3✔
3820
            case 'null':
3✔
3821
                $newToken['type'] = 'T_NULL';
3✔
3822
                break;
3✔
3823
            case 'self':
3✔
3824
                $newToken['type'] = 'T_SELF';
3✔
3825
                break;
3✔
3826
            case 'parent':
3✔
3827
                $newToken['type'] = 'T_PARENT';
3✔
3828
                break;
3✔
3829
            default:
1✔
3830
                $newToken['type'] = 'T_STRING';
3✔
3831
                break;
3✔
3832
            }
1✔
3833

3834
            $newToken['code'] = constant($newToken['type']);
3✔
3835

3836
            self::$resolveTokenCache[$cacheKey] = $newToken;
3✔
3837
        } else if ($token[0] === T_CURLY_OPEN) {
3✔
3838
            $newToken = [
3839
                'code' => T_OPEN_CURLY_BRACKET,
×
3840
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3841
            ];
3842
        } else {
3843
            $newToken = [
1✔
3844
                'code' => $token[0],
3✔
3845
                'type' => Tokens::tokenName($token[0]),
3✔
3846
            ];
2✔
3847

3848
            self::$resolveTokenCache[$token[0]] = $newToken;
3✔
3849
        }//end if
3850

3851
        $newToken['content'] = $token[1];
3✔
3852
        return $newToken;
3✔
3853

3854
    }//end standardiseToken()
3855

3856

3857
    /**
3858
     * Converts simple tokens into a format that conforms to complex tokens
3859
     * produced by token_get_all().
3860
     *
3861
     * Simple tokens are tokens that are not in array form when produced from
3862
     * token_get_all().
3863
     *
3864
     * @param string $token The simple token to convert.
3865
     *
3866
     * @return array The new token in array format.
3867
     */
3868
    public static function resolveSimpleToken($token)
3✔
3869
    {
3870
        $newToken = [];
3✔
3871

3872
        switch ($token) {
1✔
3873
        case '{':
3✔
3874
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
3✔
3875
            break;
3✔
3876
        case '}':
3✔
3877
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
3✔
3878
            break;
3✔
3879
        case '[':
3✔
3880
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
3✔
3881
            break;
3✔
3882
        case ']':
3✔
3883
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
3✔
3884
            break;
3✔
3885
        case '(':
3✔
3886
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
3✔
3887
            break;
3✔
3888
        case ')':
3✔
3889
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
3✔
3890
            break;
3✔
3891
        case ':':
3✔
3892
            $newToken['type'] = 'T_COLON';
3✔
3893
            break;
3✔
3894
        case '.':
3✔
3895
            $newToken['type'] = 'T_STRING_CONCAT';
3✔
3896
            break;
3✔
3897
        case ';':
3✔
3898
            $newToken['type'] = 'T_SEMICOLON';
3✔
3899
            break;
3✔
3900
        case '=':
3✔
3901
            $newToken['type'] = 'T_EQUAL';
3✔
3902
            break;
3✔
3903
        case '*':
3✔
3904
            $newToken['type'] = 'T_MULTIPLY';
3✔
3905
            break;
3✔
3906
        case '/':
3✔
3907
            $newToken['type'] = 'T_DIVIDE';
3✔
3908
            break;
3✔
3909
        case '+':
3✔
3910
            $newToken['type'] = 'T_PLUS';
3✔
3911
            break;
3✔
3912
        case '-':
3✔
3913
            $newToken['type'] = 'T_MINUS';
3✔
3914
            break;
3✔
3915
        case '%':
3✔
3916
            $newToken['type'] = 'T_MODULUS';
3✔
3917
            break;
3✔
3918
        case '^':
3✔
3919
            $newToken['type'] = 'T_BITWISE_XOR';
3✔
3920
            break;
3✔
3921
        case '&':
3✔
3922
            $newToken['type'] = 'T_BITWISE_AND';
2✔
3923
            break;
2✔
3924
        case '|':
3✔
3925
            $newToken['type'] = 'T_BITWISE_OR';
3✔
3926
            break;
3✔
3927
        case '~':
3✔
3928
            $newToken['type'] = 'T_BITWISE_NOT';
3✔
3929
            break;
3✔
3930
        case '<':
3✔
3931
            $newToken['type'] = 'T_LESS_THAN';
3✔
3932
            break;
3✔
3933
        case '>':
3✔
3934
            $newToken['type'] = 'T_GREATER_THAN';
3✔
3935
            break;
3✔
3936
        case '!':
3✔
3937
            $newToken['type'] = 'T_BOOLEAN_NOT';
3✔
3938
            break;
3✔
3939
        case ',':
3✔
3940
            $newToken['type'] = 'T_COMMA';
3✔
3941
            break;
3✔
3942
        case '@':
3✔
3943
            $newToken['type'] = 'T_ASPERAND';
3✔
3944
            break;
3✔
3945
        case '$':
3✔
3946
            $newToken['type'] = 'T_DOLLAR';
3✔
3947
            break;
3✔
3948
        case '`':
3✔
3949
            $newToken['type'] = 'T_BACKTICK';
3✔
3950
            break;
3✔
3951
        default:
3952
            $newToken['type'] = 'T_NONE';
×
3953
            break;
×
3954
        }//end switch
3955

3956
        $newToken['code']    = constant($newToken['type']);
3✔
3957
        $newToken['content'] = $token;
3✔
3958

3959
        self::$resolveTokenCache[$token] = $newToken;
3✔
3960
        return $newToken;
3✔
3961

3962
    }//end resolveSimpleToken()
3963

3964

3965
    /**
3966
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3967
     * Handle parenthesis balancing while searching for closing token
3968
     *
3969
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3970
     * @param int             $start        The starting position.
3971
     * @param string|string[] $openerTokens The opening character.
3972
     * @param string          $closerChar   The closing character.
3973
     *
3974
     * @return int|null The position of the closing token, if found. NULL otherwise.
3975
     */
3976
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3977
    {
3978
        $numTokens    = count($tokens);
51✔
3979
        $stack        = [0];
51✔
3980
        $closer       = null;
51✔
3981
        $openerTokens = (array) $openerTokens;
51✔
3982

3983
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3984
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3985
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3986
            ) {
17✔
3987
                $stack[] = $x;
51✔
3988
            } else if ($tokens[$x] === $closerChar) {
51✔
3989
                array_pop($stack);
51✔
3990
                if (empty($stack) === true) {
51✔
3991
                    $closer = $x;
51✔
3992
                    break;
51✔
3993
                }
3994
            }
17✔
3995
        }
17✔
3996

3997
        return $closer;
51✔
3998

3999
    }//end findCloser()
4000

4001

4002
    /**
4003
     * PHP 8 attributes parser for PHP < 8
4004
     * Handles single-line and multiline attributes.
4005
     *
4006
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
4007
     * @param int   $stackPtr The current position in token array.
4008
     *
4009
     * @return array|null The array of parsed attribute tokens
4010
     */
4011
    private function parsePhpAttribute(array &$tokens, $stackPtr)
38✔
4012
    {
4013

4014
        $token = $tokens[$stackPtr];
38✔
4015

4016
        $commentBody = substr($token[1], 2);
38✔
4017
        $subTokens   = @token_get_all('<?php '.$commentBody);
38✔
4018

4019
        foreach ($subTokens as $i => $subToken) {
38✔
4020
            if (is_array($subToken) === true
38✔
4021
                && $subToken[0] === T_COMMENT
38✔
4022
                && strpos($subToken[1], '#[') === 0
38✔
4023
            ) {
19✔
4024
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
38✔
4025
                if ($reparsed !== null) {
38✔
4026
                    array_splice($subTokens, $i, 1, $reparsed);
38✔
4027
                } else {
19✔
4028
                    $subToken[0] = T_ATTRIBUTE;
×
4029
                }
4030
            }
19✔
4031
        }
19✔
4032

4033
        array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
38✔
4034

4035
        // Go looking for the close bracket.
4036
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
38✔
4037
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
38✔
4038
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
38✔
4039
                if (is_array($token) === true) {
38✔
4040
                    $commentBody .= $token[1];
38✔
4041
                } else {
19✔
4042
                    $commentBody .= $token;
38✔
4043
                }
4044
            }
19✔
4045

4046
            $subTokens = @token_get_all('<?php '.$commentBody);
38✔
4047
            array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
38✔
4048

4049
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
38✔
4050
            if ($bracketCloser !== null) {
38✔
4051
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
38✔
4052
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
38✔
4053
            }
19✔
4054
        }
19✔
4055

4056
        if ($bracketCloser === null) {
38✔
4057
            return null;
38✔
4058
        }
4059

4060
        return $subTokens;
38✔
4061

4062
    }//end parsePhpAttribute()
4063

4064

4065
    /**
4066
     * Creates a map for the attributes tokens that surround other tokens.
4067
     *
4068
     * @return void
4069
     */
4070
    private function createAttributesNestingMap()
3✔
4071
    {
4072
        $map = [];
3✔
4073
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
4074
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
4075
                && $i === $this->tokens[$i]['attribute_opener']
3✔
4076
            ) {
1✔
4077
                if (empty($map) === false) {
3✔
4078
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4079
                }
1✔
4080

4081
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
4082
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
4083
                        = $this->tokens[$i]['attribute_closer'];
3✔
4084
                }
1✔
4085
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
4086
                && $i === $this->tokens[$i]['attribute_closer']
3✔
4087
            ) {
1✔
4088
                array_pop($map);
3✔
4089
                if (empty($map) === false) {
3✔
4090
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4091
                }
1✔
4092
            } else {
1✔
4093
                if (empty($map) === false) {
3✔
4094
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4095
                }
1✔
4096
            }//end if
4097
        }//end for
1✔
4098

4099
    }//end createAttributesNestingMap()
2✔
4100

4101

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