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

PHPCSStandards / PHP_CodeSniffer / 13935049478

18 Mar 2025 10:55PM UTC coverage: 78.721% (+0.2%) from 78.563%
13935049478

Pull #871

github

web-flow
Merge bb4e39f18 into c8288709f
Pull Request #871: PHP 8.4 | Add tokenization of asymmetric visibility

28 of 30 new or added lines in 1 file covered. (93.33%)

402 existing lines in 6 files now uncovered.

24872 of 31595 relevant lines covered (78.72%)

67.87 hits per line

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

87.23
/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
            ],
235
            'strict' => true,
236
            'shared' => true,
237
            'with'   => [
238
                T_DEFAULT => T_DEFAULT,
239
                T_CASE    => T_CASE,
240
                T_SWITCH  => T_SWITCH,
241
            ],
242
        ],
243
        T_DEFAULT       => [
244
            'start'  => [
245
                T_COLON     => T_COLON,
246
                T_SEMICOLON => T_SEMICOLON,
247
            ],
248
            'end'    => [
249
                T_BREAK    => T_BREAK,
250
                T_RETURN   => T_RETURN,
251
                T_CONTINUE => T_CONTINUE,
252
                T_THROW    => T_THROW,
253
                T_EXIT     => T_EXIT,
254
            ],
255
            'strict' => true,
256
            'shared' => true,
257
            'with'   => [
258
                T_CASE   => T_CASE,
259
                T_SWITCH => T_SWITCH,
260
            ],
261
        ],
262
        T_MATCH         => [
263
            'start'  => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
264
            'end'    => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
265
            'strict' => true,
266
            'shared' => false,
267
            'with'   => [],
268
        ],
269
        T_START_HEREDOC => [
270
            'start'  => [T_START_HEREDOC => T_START_HEREDOC],
271
            'end'    => [T_END_HEREDOC => T_END_HEREDOC],
272
            'strict' => true,
273
            'shared' => false,
274
            'with'   => [],
275
        ],
276
        T_START_NOWDOC  => [
277
            'start'  => [T_START_NOWDOC => T_START_NOWDOC],
278
            'end'    => [T_END_NOWDOC => T_END_NOWDOC],
279
            'strict' => true,
280
            'shared' => false,
281
            'with'   => [],
282
        ],
283
    ];
284

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

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

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

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

507

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

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

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

535
        $insideInlineIf         = [];
2,968✔
536
        $insideUseGroup         = false;
2,968✔
537
        $insideConstDeclaration = false;
2,968✔
538

539
        $commentTokenizer = new Comment();
2,968✔
540

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

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

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

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

567
                echo ": $type => $content";
×
568
            }//end if
569

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

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

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

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

607
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,968✔
608
                echo PHP_EOL;
×
609
            }
610

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

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

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

632
            /*
633
                Tokenize context sensitive keyword as string when it should be string.
634
            */
635

636
            if ($tokenIsArray === true
2,004✔
637
                && isset(Tokens::$contextSensitiveKeywords[$token[0]]) === true
2,968✔
638
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,871✔
639
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
2,774✔
640
                || $insideConstDeclaration === true)
2,871✔
641
            ) {
964✔
642
                if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,381✔
643
                    $preserveKeyword = false;
2,361✔
644

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

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

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

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

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

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

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

692
                            break;
524✔
693
                        }
694
                    }
243✔
695
                }//end if
750✔
696

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

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

712
                        break;
2,162✔
713
                    }
714

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

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

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

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

733
                        break;
667✔
734
                    }
735
                }
218✔
736

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

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

749
                    $newStackPtr++;
989✔
750
                    continue;
989✔
751
                }
752
            }//end if
696✔
753

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

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

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

767
            if ($insideConstDeclaration === true
2,004✔
768
                && $tokenIsArray === false
2,968✔
769
                && ($token[0] === '=' || $token[0] === ';')
2,968✔
770
            ) {
964✔
771
                $insideConstDeclaration = false;
1,120✔
772
            }
431✔
773

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

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

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

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

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

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

807
            /*
808
                Parse doc blocks into something that can be easily iterated over.
809
            */
810

811
            if ($tokenIsArray === true
2,004✔
812
                && ($token[0] === T_DOC_COMMENT
2,968✔
813
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0 && $token[1] !== '/**/'))
2,968✔
814
            ) {
964✔
815
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
93✔
816
                foreach ($commentTokens as $commentToken) {
93✔
817
                    $finalTokens[$newStackPtr] = $commentToken;
93✔
818
                    $newStackPtr++;
93✔
819
                }
31✔
820

821
                continue;
93✔
822
            }
823

824
            /*
825
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
826
            */
827

828
            if (PHP_VERSION_ID >= 80000
2,968✔
829
                && $tokenIsArray === true
2,968✔
830
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
2,968✔
831
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
832
                && is_array($tokens[($stackPtr + 1)]) === true
2,968✔
833
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
2,968✔
834
            ) {
964✔
835
                $nextToken = $tokens[($stackPtr + 1)];
772✔
836

837
                // If the next token is a single new line, merge it into the comment token
838
                // and set to it up to be skipped.
839
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
772✔
840
                    $token[1] .= $nextToken[1];
559✔
841
                    $tokens[($stackPtr + 1)] = null;
559✔
842

843
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
559✔
844
                        echo "\t\t* merged newline after comment into comment token $stackPtr".PHP_EOL;
×
845
                    }
846
                } else {
847
                    // This may be a whitespace token consisting of multiple new lines.
848
                    if (strpos($nextToken[1], "\r\n") === 0) {
605✔
849
                        $token[1] .= "\r\n";
15✔
850
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
15✔
851
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
590✔
852
                        $token[1] .= "\n\r";
×
853
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
854
                    } else if (strpos($nextToken[1], "\n") === 0) {
590✔
855
                        $token[1] .= "\n";
590✔
856
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
590✔
857
                    }
858

859
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
605✔
860
                        echo "\t\t* stripped first newline after comment and added it to comment token $stackPtr".PHP_EOL;
×
861
                    }
862
                }//end if
863
            }//end if
864

865
            /*
866
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
867
                T_LNUMBER and T_STRING token values into a single token value, and
868
                then ignore the T_STRING token.
869
            */
870

871
            if (PHP_VERSION_ID < 80100
2,968✔
872
                && $tokenIsArray === true && $token[1] === '0'
2,968✔
873
                && (isset($tokens[($stackPtr + 1)]) === true
2,524✔
874
                && is_array($tokens[($stackPtr + 1)]) === true
2,080✔
875
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,080✔
876
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,080✔
877
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,080✔
878
                && $tokens[($stackPtr + 1)][1][1] !== '_')
2,524✔
879
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
2,968✔
880
            ) {
964✔
881
                $finalTokens[$newStackPtr] = [
74✔
882
                    'code'    => T_LNUMBER,
74✔
883
                    'type'    => 'T_LNUMBER',
74✔
884
                    'content' => $token[1] .= $matches[1],
74✔
885
                ];
886
                $newStackPtr++;
74✔
887

888
                if (isset($matches[2]) === true && $matches[2] !== '') {
74✔
889
                    $type = 'T_LNUMBER';
20✔
890
                    if ($matches[2][0] === '_') {
20✔
891
                        $type = 'T_STRING';
20✔
892
                    }
10✔
893

894
                    $finalTokens[$newStackPtr] = [
20✔
895
                        'code'    => constant($type),
20✔
896
                        'type'    => $type,
20✔
897
                        'content' => $matches[2],
20✔
898
                    ];
899
                    $newStackPtr++;
20✔
900
                }
10✔
901

902
                $stackPtr++;
74✔
903
                continue;
74✔
904
            }//end if
905

906
            /*
907
                PHP 8.1 introduced two dedicated tokens for the & character.
908
                Retokenizing both of these to T_BITWISE_AND, which is the
909
                token PHPCS already tokenized them as.
910
            */
911

912
            if ($tokenIsArray === true
2,004✔
913
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
2,968✔
914
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
2,968✔
915
            ) {
964✔
916
                $finalTokens[$newStackPtr] = [
435✔
917
                    'code'    => T_BITWISE_AND,
435✔
918
                    'type'    => 'T_BITWISE_AND',
435✔
919
                    'content' => $token[1],
435✔
920
                ];
435✔
921
                $newStackPtr++;
435✔
922
                continue;
435✔
923
            }
924

925
            /*
926
                If this is a double quoted string, PHP will tokenize the whole
927
                thing which causes problems with the scope map when braces are
928
                within the string. So we need to merge the tokens together to
929
                provide a single string.
930
            */
931

932
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
2,968✔
933
                // Binary casts need a special token.
934
                if ($token[0] === 'b"') {
93✔
935
                    $finalTokens[$newStackPtr] = [
×
936
                        'code'    => T_BINARY_CAST,
×
937
                        'type'    => 'T_BINARY_CAST',
×
938
                        'content' => 'b',
×
939
                    ];
940
                    $newStackPtr++;
×
941
                }
942

943
                $tokenContent = '"';
93✔
944
                $nestedVars   = [];
93✔
945
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
93✔
946
                    $subToken        = (array) $tokens[$i];
93✔
947
                    $subTokenIsArray = isset($subToken[1]);
93✔
948

949
                    if ($subTokenIsArray === true) {
93✔
950
                        $tokenContent .= $subToken[1];
93✔
951
                        if (($subToken[1] === '{'
93✔
952
                            || $subToken[1] === '${')
93✔
953
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
93✔
954
                        ) {
31✔
955
                            $nestedVars[] = $i;
71✔
956
                        }
20✔
957
                    } else {
31✔
958
                        $tokenContent .= $subToken[0];
93✔
959
                        if ($subToken[0] === '}') {
93✔
960
                            array_pop($nestedVars);
60✔
961
                        }
20✔
962
                    }
963

964
                    if ($subTokenIsArray === false
62✔
965
                        && $subToken[0] === '"'
93✔
966
                        && empty($nestedVars) === true
93✔
967
                    ) {
31✔
968
                        // We found the other end of the double quoted string.
969
                        break;
93✔
970
                    }
971
                }//end for
31✔
972

973
                $stackPtr = $i;
93✔
974

975
                // Convert each line within the double quoted string to a
976
                // new token, so it conforms with other multiple line tokens.
977
                $tokenLines = explode($this->eolChar, $tokenContent);
93✔
978
                $numLines   = count($tokenLines);
93✔
979
                $newToken   = [];
93✔
980

981
                for ($j = 0; $j < $numLines; $j++) {
93✔
982
                    $newToken['content'] = $tokenLines[$j];
93✔
983
                    if ($j === ($numLines - 1)) {
93✔
984
                        if ($tokenLines[$j] === '') {
93✔
985
                            break;
71✔
986
                        }
987
                    } else {
31✔
988
                        $newToken['content'] .= $this->eolChar;
60✔
989
                    }
990

991
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
93✔
992
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
93✔
993
                    $finalTokens[$newStackPtr] = $newToken;
93✔
994
                    $newStackPtr++;
93✔
995
                }
31✔
996

997
                // Continue, as we're done with this token.
998
                continue;
93✔
999
            }//end if
1000

1001
            /*
1002
                Detect binary casting and assign the casts their own token.
1003
            */
1004

1005
            if ($tokenIsArray === true
2,004✔
1006
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
2,968✔
1007
                && (substr($token[1], 0, 2) === 'b"'
2,865✔
1008
                || substr($token[1], 0, 2) === "b'")
2,865✔
1009
            ) {
964✔
1010
                $finalTokens[$newStackPtr] = [
×
1011
                    'code'    => T_BINARY_CAST,
×
1012
                    'type'    => 'T_BINARY_CAST',
×
1013
                    'content' => 'b',
×
1014
                ];
1015
                $newStackPtr++;
×
1016
                $token[1] = substr($token[1], 1);
×
1017
            }
1018

1019
            if ($tokenIsArray === true
2,004✔
1020
                && $token[0] === T_STRING_CAST
2,968✔
1021
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
2,968✔
1022
            ) {
964✔
1023
                $finalTokens[$newStackPtr] = [
×
1024
                    'code'    => T_BINARY_CAST,
×
1025
                    'type'    => 'T_BINARY_CAST',
×
1026
                    'content' => $token[1],
×
1027
                ];
1028
                $newStackPtr++;
×
1029
                continue;
×
1030
            }
1031

1032
            /*
1033
                If this is a heredoc, PHP will tokenize the whole
1034
                thing which causes problems when heredocs don't
1035
                contain real PHP code, which is almost never.
1036
                We want to leave the start and end heredoc tokens
1037
                alone though.
1038
            */
1039

1040
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
2,968✔
1041
                // Add the start heredoc token to the final array.
1042
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
135✔
1043

1044
                // Check if this is actually a nowdoc and use a different token
1045
                // to help the sniffs.
1046
                $nowdoc = false;
135✔
1047
                if (strpos($token[1], "'") !== false) {
135✔
1048
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
18✔
1049
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
18✔
1050
                    $nowdoc = true;
18✔
1051
                }
6✔
1052

1053
                $tokenContent = '';
135✔
1054
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
135✔
1055
                    $subTokenIsArray = is_array($tokens[$i]);
135✔
1056
                    if ($subTokenIsArray === true
90✔
1057
                        && $tokens[$i][0] === T_END_HEREDOC
135✔
1058
                    ) {
45✔
1059
                        // We found the other end of the heredoc.
1060
                        break;
132✔
1061
                    }
1062

1063
                    if ($subTokenIsArray === true) {
135✔
1064
                        $tokenContent .= $tokens[$i][1];
135✔
1065
                    } else {
45✔
1066
                        $tokenContent .= $tokens[$i];
114✔
1067
                    }
1068
                }
45✔
1069

1070
                if ($i === $numTokens) {
135✔
1071
                    // We got to the end of the file and never
1072
                    // found the closing token, so this probably wasn't
1073
                    // a heredoc.
1074
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
1075
                        $type = $finalTokens[$newStackPtr]['type'];
×
1076
                        echo "\t\t* failed to find the end of the here/nowdoc".PHP_EOL;
×
1077
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
1078
                    }
1079

1080
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
3✔
1081
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
3✔
1082
                    $newStackPtr++;
3✔
1083
                    continue;
3✔
1084
                }
1085

1086
                $stackPtr = $i;
132✔
1087
                $newStackPtr++;
132✔
1088

1089
                // Convert each line within the heredoc to a
1090
                // new token, so it conforms with other multiple line tokens.
1091
                $tokenLines = explode($this->eolChar, $tokenContent);
132✔
1092
                $numLines   = count($tokenLines);
132✔
1093
                $newToken   = [];
132✔
1094

1095
                for ($j = 0; $j < $numLines; $j++) {
132✔
1096
                    $newToken['content'] = $tokenLines[$j];
132✔
1097
                    if ($j === ($numLines - 1)) {
132✔
1098
                        if ($tokenLines[$j] === '') {
132✔
1099
                            break;
132✔
1100
                        }
1101
                    } else {
1102
                        $newToken['content'] .= $this->eolChar;
132✔
1103
                    }
1104

1105
                    if ($nowdoc === true) {
132✔
1106
                        $newToken['code'] = T_NOWDOC;
18✔
1107
                        $newToken['type'] = 'T_NOWDOC';
18✔
1108
                    } else {
6✔
1109
                        $newToken['code'] = T_HEREDOC;
132✔
1110
                        $newToken['type'] = 'T_HEREDOC';
132✔
1111
                    }
1112

1113
                    $finalTokens[$newStackPtr] = $newToken;
132✔
1114
                    $newStackPtr++;
132✔
1115
                }//end for
44✔
1116

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

1120
                if ($nowdoc === true) {
132✔
1121
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
18✔
1122
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
18✔
1123
                }
6✔
1124

1125
                $newStackPtr++;
132✔
1126

1127
                // Continue, as we're done with this token.
1128
                continue;
132✔
1129
            }//end if
1130

1131
            /*
1132
                Enum keyword for PHP < 8.1
1133
            */
1134

1135
            if ($tokenIsArray === true
2,004✔
1136
                && $token[0] === T_STRING
2,968✔
1137
                && strtolower($token[1]) === 'enum'
2,968✔
1138
            ) {
964✔
1139
                // Get the next non-empty token.
1140
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,397✔
1141
                    if (is_array($tokens[$i]) === false
1,397✔
1142
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,397✔
1143
                    ) {
465✔
1144
                        break;
1,397✔
1145
                    }
1146
                }
252✔
1147

1148
                if (isset($tokens[$i]) === true
1,397✔
1149
                    && is_array($tokens[$i]) === true
1,397✔
1150
                    && $tokens[$i][0] === T_STRING
1,397✔
1151
                ) {
465✔
1152
                    // Modify $tokens directly so we can use it later when converting enum "case".
1153
                    $tokens[$stackPtr][0] = T_ENUM;
542✔
1154

1155
                    $newToken            = [];
542✔
1156
                    $newToken['code']    = T_ENUM;
542✔
1157
                    $newToken['type']    = 'T_ENUM';
542✔
1158
                    $newToken['content'] = $token[1];
542✔
1159
                    $finalTokens[$newStackPtr] = $newToken;
542✔
1160

1161
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
542✔
1162
                        echo "\t\t* token $stackPtr changed from T_STRING to T_ENUM".PHP_EOL;
×
1163
                    }
1164

1165
                    $newStackPtr++;
542✔
1166
                    continue;
542✔
1167
                }
1168
            }//end if
429✔
1169

1170
            /*
1171
                Convert enum "case" to T_ENUM_CASE
1172
            */
1173

1174
            if ($tokenIsArray === true
2,004✔
1175
                && $token[0] === T_CASE
2,968✔
1176
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,968✔
1177
            ) {
964✔
1178
                $isEnumCase = false;
1,551✔
1179
                $scope      = 1;
1,551✔
1180

1181
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,551✔
1182
                    if ($tokens[$i] === '}') {
1,551✔
1183
                        $scope++;
1,518✔
1184
                        continue;
1,518✔
1185
                    }
1186

1187
                    if ($tokens[$i] === '{') {
1,551✔
1188
                        $scope--;
1,551✔
1189
                        continue;
1,551✔
1190
                    }
1191

1192
                    if (is_array($tokens[$i]) === false) {
1,551✔
1193
                        continue;
1,551✔
1194
                    }
1195

1196
                    if ($scope !== 0) {
1,551✔
1197
                        continue;
1,551✔
1198
                    }
1199

1200
                    if ($tokens[$i][0] === T_SWITCH) {
1,032✔
1201
                        break;
966✔
1202
                    }
1203

1204
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,032✔
1205
                        $isEnumCase = true;
129✔
1206
                        break;
129✔
1207
                    }
1208
                }//end for
344✔
1209

1210
                if ($isEnumCase === true) {
1,551✔
1211
                    // Modify $tokens directly so we can use it as optimisation for other enum "case".
1212
                    $tokens[$stackPtr][0] = T_ENUM_CASE;
129✔
1213

1214
                    $newToken            = [];
129✔
1215
                    $newToken['code']    = T_ENUM_CASE;
129✔
1216
                    $newToken['type']    = 'T_ENUM_CASE';
129✔
1217
                    $newToken['content'] = $token[1];
129✔
1218
                    $finalTokens[$newStackPtr] = $newToken;
129✔
1219

1220
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
129✔
1221
                        echo "\t\t* token $stackPtr changed from T_CASE to T_ENUM_CASE".PHP_EOL;
×
1222
                    }
1223

1224
                    $newStackPtr++;
129✔
1225
                    continue;
129✔
1226
                }
1227
            }//end if
495✔
1228

1229
            /*
1230
                Asymmetric visibility for PHP < 8.4
1231
            */
1232

1233
            if ($tokenIsArray === true
2,004✔
1234
                && in_array($token[0], [T_PUBLIC, T_PROTECTED, T_PRIVATE], true) === true
2,968✔
1235
                && ($stackPtr + 3) < $numTokens
2,968✔
1236
                && $tokens[($stackPtr + 1)] === '('
2,968✔
1237
                && is_array($tokens[($stackPtr + 2)]) === true
2,968✔
1238
                && $tokens[($stackPtr + 2)][0] === T_STRING
2,968✔
1239
                && strtolower($tokens[($stackPtr + 2)][1]) === 'set'
2,968✔
1240
                && $tokens[($stackPtr + 3)] === ')'
2,968✔
1241
            ) {
964✔
1242
                $newToken = [];
346✔
1243
                if ($token[0] === T_PUBLIC) {
346✔
1244
                    $oldCode          = 'T_PUBLIC';
346✔
1245
                    $newToken['code'] = T_PUBLIC_SET;
346✔
1246
                    $newToken['type'] = 'T_PUBLIC_SET';
346✔
1247
                } else if ($token[0] === T_PROTECTED) {
346✔
1248
                    $oldCode          = 'T_PROTECTED';
346✔
1249
                    $newToken['code'] = T_PROTECTED_SET;
346✔
1250
                    $newToken['type'] = 'T_PROTECTED_SET';
346✔
1251
                } else {
173✔
1252
                    $oldCode          = 'T_PRIVATE';
346✔
1253
                    $newToken['code'] = T_PRIVATE_SET;
346✔
1254
                    $newToken['type'] = 'T_PRIVATE_SET';
346✔
1255
                }
1256

1257
                $newToken['content']       = $token[1].'('.$tokens[($stackPtr + 2)][1].')';
346✔
1258
                $finalTokens[$newStackPtr] = $newToken;
346✔
1259
                $newStackPtr++;
346✔
1260

1261
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
346✔
NEW
1262
                    $newCode = $newToken['type'];
×
NEW
1263
                    echo "\t\t* tokens from $stackPtr changed from $oldCode to $newCode".PHP_EOL;
×
1264
                }
1265

1266
                // We processed an extra 3 tokens, for `(`, `set`, and `)`.
1267
                $stackPtr += 3;
346✔
1268

1269
                continue;
346✔
1270
            }//end if
1271

1272
            /*
1273
                As of PHP 8.0 fully qualified, partially qualified and namespace relative
1274
                identifier names are tokenized differently.
1275
                This "undoes" the new tokenization so the tokenization will be the same in
1276
                in PHP 5, 7 and 8.
1277
            */
1278

1279
            if (PHP_VERSION_ID >= 80000
2,968✔
1280
                && $tokenIsArray === true
2,968✔
1281
                && ($token[0] === T_NAME_QUALIFIED
1,966✔
1282
                || $token[0] === T_NAME_FULLY_QUALIFIED
1,002✔
1283
                || $token[0] === T_NAME_RELATIVE)
2,004✔
1284
            ) {
964✔
1285
                $name = $token[1];
621✔
1286

1287
                if ($token[0] === T_NAME_FULLY_QUALIFIED) {
621✔
1288
                    $newToken            = [];
354✔
1289
                    $newToken['code']    = T_NS_SEPARATOR;
354✔
1290
                    $newToken['type']    = 'T_NS_SEPARATOR';
354✔
1291
                    $newToken['content'] = '\\';
354✔
1292
                    $finalTokens[$newStackPtr] = $newToken;
354✔
1293
                    ++$newStackPtr;
354✔
1294

1295
                    $name = ltrim($name, '\\');
354✔
1296
                }
1297

1298
                if ($token[0] === T_NAME_RELATIVE) {
621✔
1299
                    $newToken            = [];
593✔
1300
                    $newToken['code']    = T_NAMESPACE;
593✔
1301
                    $newToken['type']    = 'T_NAMESPACE';
593✔
1302
                    $newToken['content'] = substr($name, 0, 9);
593✔
1303
                    $finalTokens[$newStackPtr] = $newToken;
593✔
1304
                    ++$newStackPtr;
593✔
1305

1306
                    $newToken            = [];
593✔
1307
                    $newToken['code']    = T_NS_SEPARATOR;
593✔
1308
                    $newToken['type']    = 'T_NS_SEPARATOR';
593✔
1309
                    $newToken['content'] = '\\';
593✔
1310
                    $finalTokens[$newStackPtr] = $newToken;
593✔
1311
                    ++$newStackPtr;
593✔
1312

1313
                    $name = substr($name, 10);
593✔
1314
                }
1315

1316
                $parts     = explode('\\', $name);
621✔
1317
                $partCount = count($parts);
621✔
1318
                $lastPart  = ($partCount - 1);
621✔
1319

1320
                foreach ($parts as $i => $part) {
621✔
1321
                    $newToken            = [];
621✔
1322
                    $newToken['code']    = T_STRING;
621✔
1323
                    $newToken['type']    = 'T_STRING';
621✔
1324
                    $newToken['content'] = $part;
621✔
1325
                    $finalTokens[$newStackPtr] = $newToken;
621✔
1326
                    ++$newStackPtr;
621✔
1327

1328
                    if ($i !== $lastPart) {
621✔
1329
                        $newToken            = [];
448✔
1330
                        $newToken['code']    = T_NS_SEPARATOR;
448✔
1331
                        $newToken['type']    = 'T_NS_SEPARATOR';
448✔
1332
                        $newToken['content'] = '\\';
448✔
1333
                        $finalTokens[$newStackPtr] = $newToken;
448✔
1334
                        ++$newStackPtr;
448✔
1335
                    }
1336
                }
1337

1338
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
621✔
1339
                    $type    = Tokens::tokenName($token[0]);
×
1340
                    $content = Common::prepareForOutput($token[1]);
×
1341
                    echo "\t\t* token $stackPtr split into individual tokens; was: $type => $content".PHP_EOL;
×
1342
                }
1343

1344
                continue;
621✔
1345
            }//end if
1346

1347
            /*
1348
                PHP 8.0 Attributes
1349
            */
1350

1351
            if (PHP_VERSION_ID < 80000
2,968✔
1352
                && $token[0] === T_COMMENT
2,968✔
1353
                && strpos($token[1], '#[') === 0
2,968✔
1354
            ) {
964✔
1355
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
34✔
1356
                if ($subTokens !== null) {
34✔
1357
                    array_splice($tokens, $stackPtr, 1, $subTokens);
34✔
1358
                    $numTokens = count($tokens);
34✔
1359

1360
                    $tokenIsArray = true;
34✔
1361
                    $token        = $tokens[$stackPtr];
34✔
1362
                } else {
17✔
1363
                    $token[0] = T_ATTRIBUTE;
34✔
1364
                }
1365
            }
17✔
1366

1367
            if ($tokenIsArray === true
2,004✔
1368
                && $token[0] === T_ATTRIBUTE
2,968✔
1369
            ) {
964✔
1370
                // Go looking for the close bracket.
1371
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1372

1373
                $newToken            = [];
51✔
1374
                $newToken['code']    = T_ATTRIBUTE;
51✔
1375
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1376
                $newToken['content'] = '#[';
51✔
1377
                $finalTokens[$newStackPtr] = $newToken;
51✔
1378

1379
                $tokens[$bracketCloser]    = [];
51✔
1380
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1381
                $tokens[$bracketCloser][1] = ']';
51✔
1382

1383
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1384
                    echo "\t\t* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END".PHP_EOL;
×
1385
                }
1386

1387
                $newStackPtr++;
51✔
1388
                continue;
51✔
1389
            }//end if
1390

1391
            /*
1392
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1393
                token and ensures that the colon after it is always T_COLON.
1394
            */
1395

1396
            if ($tokenIsArray === true
2,004✔
1397
                && ($token[0] === T_STRING
2,968✔
1398
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
2,968✔
1399
            ) {
964✔
1400
                // Get the next non-empty token.
1401
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,791✔
1402
                    if (is_array($tokens[$i]) === false
2,791✔
1403
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
2,791✔
1404
                    ) {
905✔
1405
                        break;
2,791✔
1406
                    }
1407
                }
867✔
1408

1409
                if (isset($tokens[$i]) === true
2,791✔
1410
                    && is_array($tokens[$i]) === false
2,791✔
1411
                    && $tokens[$i] === ':'
2,791✔
1412
                ) {
905✔
1413
                    // Get the previous non-empty token.
1414
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,623✔
1415
                        if (is_array($tokens[$j]) === false
1,623✔
1416
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
1,623✔
1417
                        ) {
541✔
1418
                            break;
1,623✔
1419
                        }
1420
                    }
534✔
1421

1422
                    if (is_array($tokens[$j]) === false
1,623✔
1423
                        && ($tokens[$j] === '('
1,573✔
1424
                        || $tokens[$j] === ',')
1,573✔
1425
                    ) {
541✔
1426
                        $newToken            = [];
690✔
1427
                        $newToken['code']    = T_PARAM_NAME;
690✔
1428
                        $newToken['type']    = 'T_PARAM_NAME';
690✔
1429
                        $newToken['content'] = $token[1];
690✔
1430
                        $finalTokens[$newStackPtr] = $newToken;
690✔
1431

1432
                        $newStackPtr++;
690✔
1433

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

1438
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
690✔
1439
                            $type = Tokens::tokenName($token[0]);
×
1440
                            echo "\t\t* token $stackPtr changed from $type to T_PARAM_NAME".PHP_EOL;
×
1441
                        }
1442

1443
                        continue;
690✔
1444
                    }
1445
                }//end if
524✔
1446
            }//end if
905✔
1447

1448
            /*
1449
                "readonly" keyword for PHP < 8.1
1450
            */
1451

1452
            if ($tokenIsArray === true
2,004✔
1453
                && strtolower($token[1]) === 'readonly'
2,968✔
1454
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,299✔
1455
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
2,337✔
1456
            ) {
964✔
1457
                // Get the next non-whitespace token.
1458
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
999✔
1459
                    if (is_array($tokens[$i]) === false
999✔
1460
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
999✔
1461
                    ) {
333✔
1462
                        break;
999✔
1463
                    }
1464
                }
333✔
1465

1466
                $isReadonlyKeyword = false;
999✔
1467

1468
                if (isset($tokens[$i]) === false
999✔
1469
                    || $tokens[$i] !== '('
999✔
1470
                ) {
333✔
1471
                    $isReadonlyKeyword = true;
999✔
1472
                } else if ($tokens[$i] === '(') {
333✔
1473
                    /*
1474
                     * Skip over tokens which can be used in type declarations.
1475
                     * At this point, the only token types which need to be taken into consideration
1476
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1477
                     * and the union/intersection/dnf parentheses.
1478
                     */
1479

1480
                    $foundDNFParens = 1;
×
1481
                    $foundDNFPipe   = 0;
×
1482

1483
                    for (++$i; $i < $numTokens; $i++) {
×
1484
                        if (is_array($tokens[$i]) === true) {
×
1485
                            $tokenType = $tokens[$i][0];
×
1486
                        } else {
1487
                            $tokenType = $tokens[$i];
×
1488
                        }
1489

1490
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1491
                            continue;
×
1492
                        }
1493

1494
                        if ($tokenType === '|') {
×
1495
                            ++$foundDNFPipe;
×
1496
                            continue;
×
1497
                        }
1498

1499
                        if ($tokenType === ')') {
×
1500
                            ++$foundDNFParens;
×
1501
                            continue;
×
1502
                        }
1503

1504
                        if ($tokenType === '(') {
×
1505
                            ++$foundDNFParens;
×
1506
                            continue;
×
1507
                        }
1508

1509
                        if ($tokenType === T_STRING
1510
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1511
                            || $tokenType === T_NAME_RELATIVE
×
1512
                            || $tokenType === T_NAME_QUALIFIED
×
1513
                            || $tokenType === T_ARRAY
×
1514
                            || $tokenType === T_NAMESPACE
×
1515
                            || $tokenType === T_NS_SEPARATOR
×
1516
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1517
                            || $tokenType === '&' // PHP < 8.0.
×
1518
                        ) {
1519
                            continue;
×
1520
                        }
1521

1522
                        // Reached the next token after.
1523
                        if (($foundDNFParens % 2) === 0
×
1524
                            && $foundDNFPipe >= 1
×
1525
                            && ($tokenType === T_VARIABLE
×
1526
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1527
                        ) {
1528
                            $isReadonlyKeyword = true;
×
1529
                        }
1530

1531
                        break;
×
1532
                    }//end for
1533
                }//end if
1534

1535
                if ($isReadonlyKeyword === true) {
999✔
1536
                    $finalTokens[$newStackPtr] = [
999✔
1537
                        'code'    => T_READONLY,
999✔
1538
                        'type'    => 'T_READONLY',
999✔
1539
                        'content' => $token[1],
999✔
1540
                    ];
333✔
1541
                    $newStackPtr++;
999✔
1542

1543
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
999✔
1544
                        echo "\t\t* token $stackPtr changed from $type to T_READONLY".PHP_EOL;
333✔
1545
                    }
1546
                } else {
333✔
1547
                    $finalTokens[$newStackPtr] = [
×
1548
                        'code'    => T_STRING,
×
1549
                        'type'    => 'T_STRING',
×
1550
                        'content' => $token[1],
×
1551
                    ];
1552
                    $newStackPtr++;
×
1553

1554
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1555
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
1556
                    }
1557
                }//end if
1558

1559
                continue;
999✔
1560
            }//end if
1561

1562
            /*
1563
                Before PHP 7.0, "yield from" was tokenized as
1564
                T_YIELD, T_WHITESPACE and T_STRING. So look for
1565
                and change this token in earlier versions.
1566
            */
1567

1568
            if (PHP_VERSION_ID < 70000
2,968✔
1569
                && $tokenIsArray === true
2,968✔
1570
                && $token[0] === T_YIELD
2,968✔
1571
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1572
                && isset($tokens[($stackPtr + 2)]) === true
2,968✔
1573
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
2,968✔
1574
                && strpos($tokens[($stackPtr + 1)][1], $this->eolChar) === false
2,968✔
1575
                && $tokens[($stackPtr + 2)][0] === T_STRING
2,968✔
1576
                && strtolower($tokens[($stackPtr + 2)][1]) === 'from'
2,968✔
1577
            ) {
964✔
1578
                // Single-line "yield from" with only whitespace between.
1579
                $finalTokens[$newStackPtr] = [
193✔
1580
                    'code'    => T_YIELD_FROM,
193✔
1581
                    'type'    => 'T_YIELD_FROM',
193✔
1582
                    'content' => $token[1].$tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1],
193✔
1583
                ];
1584

1585
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
193✔
1586
                    for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) {
×
1587
                        $type    = Tokens::tokenName($tokens[$i][0]);
×
1588
                        $content = Common::prepareForOutput($tokens[$i][1]);
×
1589
                        echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL;
×
1590
                    }
1591
                }
1592

1593
                $newStackPtr++;
193✔
1594
                $stackPtr += 2;
193✔
1595

1596
                continue;
193✔
1597
            } else if (PHP_VERSION_ID < 80300
2,968✔
1598
                && $tokenIsArray === true
2,968✔
1599
                && $token[0] === T_STRING
2,968✔
1600
                && strtolower($token[1]) === 'from'
2,968✔
1601
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
2,968✔
1602
            ) {
964✔
1603
                /*
1604
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1605
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1606
                    We want to keep the tokenization of the tokens between, but need to change the
1607
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1608
                */
1609

1610
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
40✔
1611
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
40✔
1612

1613
                $finalTokens[$newStackPtr] = [
40✔
1614
                    'code'    => T_YIELD_FROM,
40✔
1615
                    'type'    => 'T_YIELD_FROM',
40✔
1616
                    'content' => $token[1],
40✔
1617
                ];
1618
                $newStackPtr++;
40✔
1619

1620
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
40✔
1621
                    echo "\t\t* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD".PHP_EOL;
×
1622
                    echo "\t\t* token $stackPtr changed into T_YIELD_FROM; was: T_STRING".PHP_EOL;
×
1623
                }
1624

1625
                continue;
40✔
1626
            } else if (PHP_VERSION_ID >= 70000
2,968✔
1627
                && $tokenIsArray === true
2,968✔
1628
                && $token[0] === T_YIELD_FROM
2,968✔
1629
                && strpos($token[1], $this->eolChar) !== false
2,968✔
1630
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
2,968✔
1631
            ) {
964✔
1632
                /*
1633
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1634
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1635
                    separately for consistency.
1636
                */
1637

1638
                $finalTokens[$newStackPtr] = [
40✔
1639
                    'code'    => T_YIELD_FROM,
40✔
1640
                    'type'    => 'T_YIELD_FROM',
40✔
1641
                    'content' => substr($token[1], 0, 5),
40✔
1642
                ];
20✔
1643
                $newStackPtr++;
40✔
1644

1645
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
40✔
1646
                $numLines   = count($tokenLines);
40✔
1647
                $newToken   = [
20✔
1648
                    'type'    => 'T_WHITESPACE',
40✔
1649
                    'code'    => T_WHITESPACE,
40✔
1650
                    'content' => '',
40✔
1651
                ];
20✔
1652

1653
                foreach ($tokenLines as $i => $line) {
40✔
1654
                    $newToken['content'] = $line;
40✔
1655
                    if ($i === ($numLines - 1)) {
40✔
1656
                        if ($line === '') {
40✔
1657
                            break;
20✔
1658
                        }
1659
                    } else {
1660
                        $newToken['content'] .= $this->eolChar;
40✔
1661
                    }
1662

1663
                    $finalTokens[$newStackPtr] = $newToken;
40✔
1664
                    $newStackPtr++;
40✔
1665
                }
1666

1667
                $finalTokens[$newStackPtr] = [
40✔
1668
                    'code'    => T_YIELD_FROM,
40✔
1669
                    'type'    => 'T_YIELD_FROM',
40✔
1670
                    'content' => substr($token[1], -4),
40✔
1671
                ];
20✔
1672
                $newStackPtr++;
40✔
1673

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

1678
                continue;
40✔
1679
            } else if (PHP_VERSION_ID >= 80300
2,968✔
1680
                && $tokenIsArray === true
2,968✔
1681
                && $token[0] === T_YIELD_FROM
2,968✔
1682
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
2,968✔
1683
                && stripos($token[1], 'yield') === 0
2,968✔
1684
            ) {
964✔
1685
                /*
1686
                    Since PHP 8.3, "yield from" allows for comments and will
1687
                    swallow the comment in the `T_YIELD_FROM` token.
1688
                    We need to split this up to allow for sniffs handling comments.
1689
                */
1690

1691
                $finalTokens[$newStackPtr] = [
20✔
1692
                    'code'    => T_YIELD_FROM,
20✔
1693
                    'type'    => 'T_YIELD_FROM',
20✔
1694
                    'content' => substr($token[1], 0, 5),
20✔
1695
                ];
20✔
1696
                $newStackPtr++;
20✔
1697

1698
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
20✔
1699
                // Remove the PHP open tag token.
1700
                array_shift($yieldFromSubtokens);
20✔
1701
                // Add the "from" keyword.
1702
                $yieldFromSubtokens[] = [
20✔
1703
                    0 => T_YIELD_FROM,
20✔
1704
                    1 => substr($token[1], -4),
20✔
1705
                ];
20✔
1706

1707
                // Inject the new tokens into the token stack.
1708
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
20✔
1709
                $numTokens = count($tokens);
20✔
1710

1711
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
20✔
1712
                    echo "\t\t* token $stackPtr split into parts (yield from with comment)".PHP_EOL;
×
1713
                }
1714

1715
                unset($yieldFromSubtokens);
20✔
1716
                continue;
20✔
1717
            }//end if
1718

1719
            /*
1720
                Before PHP 5.6, the ... operator was tokenized as three
1721
                T_STRING_CONCAT tokens in a row. So look for and combine
1722
                these tokens in earlier versions.
1723
            */
1724

1725
            if ($tokenIsArray === false
2,004✔
1726
                && $token[0] === '.'
2,968✔
1727
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1728
                && isset($tokens[($stackPtr + 2)]) === true
2,968✔
1729
                && $tokens[($stackPtr + 1)] === '.'
2,968✔
1730
                && $tokens[($stackPtr + 2)] === '.'
2,968✔
1731
            ) {
964✔
1732
                $newToken            = [];
213✔
1733
                $newToken['code']    = T_ELLIPSIS;
213✔
1734
                $newToken['type']    = 'T_ELLIPSIS';
213✔
1735
                $newToken['content'] = '...';
213✔
1736
                $finalTokens[$newStackPtr] = $newToken;
213✔
1737

1738
                $newStackPtr++;
213✔
1739
                $stackPtr += 2;
213✔
1740
                continue;
213✔
1741
            }
1742

1743
            /*
1744
                Before PHP 5.6, the ** operator was tokenized as two
1745
                T_MULTIPLY tokens in a row. So look for and combine
1746
                these tokens in earlier versions.
1747
            */
1748

1749
            if ($tokenIsArray === false
2,004✔
1750
                && $token[0] === '*'
2,968✔
1751
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1752
                && $tokens[($stackPtr + 1)] === '*'
2,968✔
1753
            ) {
964✔
1754
                $newToken            = [];
×
1755
                $newToken['code']    = T_POW;
×
1756
                $newToken['type']    = 'T_POW';
×
1757
                $newToken['content'] = '**';
×
1758
                $finalTokens[$newStackPtr] = $newToken;
×
1759

1760
                $newStackPtr++;
×
1761
                $stackPtr++;
×
1762
                continue;
×
1763
            }
1764

1765
            /*
1766
                Before PHP 5.6, the **= operator was tokenized as
1767
                T_MULTIPLY followed by T_MUL_EQUAL. So look for and combine
1768
                these tokens in earlier versions.
1769
            */
1770

1771
            if ($tokenIsArray === false
2,004✔
1772
                && $token[0] === '*'
2,968✔
1773
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1774
                && is_array($tokens[($stackPtr + 1)]) === true
2,968✔
1775
                && $tokens[($stackPtr + 1)][1] === '*='
2,968✔
1776
            ) {
964✔
1777
                $newToken            = [];
×
1778
                $newToken['code']    = T_POW_EQUAL;
×
1779
                $newToken['type']    = 'T_POW_EQUAL';
×
1780
                $newToken['content'] = '**=';
×
1781
                $finalTokens[$newStackPtr] = $newToken;
×
1782

1783
                $newStackPtr++;
×
1784
                $stackPtr++;
×
1785
                continue;
×
1786
            }
1787

1788
            /*
1789
                Before PHP 7, the ??= operator was tokenized as
1790
                T_INLINE_THEN, T_INLINE_THEN, T_EQUAL.
1791
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1792
                T_COALESCE, T_EQUAL.
1793
                So look for and combine these tokens in earlier versions.
1794
            */
1795

1796
            if (($tokenIsArray === false
2,004✔
1797
                && $token[0] === '?'
2,968✔
1798
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1799
                && $tokens[($stackPtr + 1)][0] === '?'
2,968✔
1800
                && isset($tokens[($stackPtr + 2)]) === true
2,968✔
1801
                && $tokens[($stackPtr + 2)][0] === '=')
964✔
1802
                || ($tokenIsArray === true
2,004✔
1803
                && $token[0] === T_COALESCE
2,968✔
1804
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1805
                && $tokens[($stackPtr + 1)][0] === '=')
2,968✔
1806
            ) {
964✔
1807
                $newToken            = [];
×
1808
                $newToken['code']    = T_COALESCE_EQUAL;
×
1809
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1810
                $newToken['content'] = '??=';
×
1811
                $finalTokens[$newStackPtr] = $newToken;
×
1812

1813
                $newStackPtr++;
×
1814
                $stackPtr++;
×
1815

1816
                if ($tokenIsArray === false) {
×
1817
                    // Pre PHP 7.
1818
                    $stackPtr++;
×
1819
                }
1820

1821
                continue;
×
1822
            }
1823

1824
            /*
1825
                Before PHP 7, the ?? operator was tokenized as
1826
                T_INLINE_THEN followed by T_INLINE_THEN.
1827
                So look for and combine these tokens in earlier versions.
1828
            */
1829

1830
            if ($tokenIsArray === false
2,004✔
1831
                && $token[0] === '?'
2,968✔
1832
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1833
                && $tokens[($stackPtr + 1)][0] === '?'
2,968✔
1834
            ) {
964✔
1835
                $newToken            = [];
61✔
1836
                $newToken['code']    = T_COALESCE;
61✔
1837
                $newToken['type']    = 'T_COALESCE';
61✔
1838
                $newToken['content'] = '??';
61✔
1839
                $finalTokens[$newStackPtr] = $newToken;
61✔
1840

1841
                $newStackPtr++;
61✔
1842
                $stackPtr++;
61✔
1843
                continue;
61✔
1844
            }
1845

1846
            /*
1847
                Before PHP 8, the ?-> operator was tokenized as
1848
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1849
                So look for and combine these tokens in earlier versions.
1850
            */
1851

1852
            if ($tokenIsArray === false
2,004✔
1853
                && $token[0] === '?'
2,968✔
1854
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1855
                && is_array($tokens[($stackPtr + 1)]) === true
2,968✔
1856
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
2,968✔
1857
            ) {
964✔
1858
                $newToken            = [];
54✔
1859
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
54✔
1860
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
54✔
1861
                $newToken['content'] = '?->';
54✔
1862
                $finalTokens[$newStackPtr] = $newToken;
54✔
1863

1864
                $newStackPtr++;
54✔
1865
                $stackPtr++;
54✔
1866
                continue;
54✔
1867
            }
1868

1869
            /*
1870
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1871
                tokens split the token with a T_STRING. So look for
1872
                and change these tokens in earlier versions.
1873
            */
1874

1875
            if (PHP_VERSION_ID < 70400
2,968✔
1876
                && ($tokenIsArray === true
2,968✔
1877
                && ($token[0] === T_LNUMBER
2,968✔
1878
                || $token[0] === T_DNUMBER)
2,968✔
1879
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
1880
                && is_array($tokens[($stackPtr + 1)]) === true
2,968✔
1881
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,968✔
1882
                && $tokens[($stackPtr + 1)][1][0] === '_')
2,968✔
1883
            ) {
964✔
1884
                $newContent = $token[1];
54✔
1885
                $newType    = $token[0];
54✔
1886
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
54✔
1887
                    if (is_array($tokens[$i]) === false) {
54✔
1888
                        break;
54✔
1889
                    }
1890

1891
                    if ($tokens[$i][0] === T_LNUMBER
54✔
1892
                        || $tokens[$i][0] === T_DNUMBER
54✔
1893
                    ) {
27✔
1894
                        $newContent .= $tokens[$i][1];
54✔
1895
                        continue;
54✔
1896
                    }
1897

1898
                    if ($tokens[$i][0] === T_STRING
54✔
1899
                        && $tokens[$i][1][0] === '_'
54✔
1900
                        && ((strpos($newContent, '0x') === 0
54✔
1901
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
54✔
1902
                        || (strpos($newContent, '0x') !== 0
54✔
1903
                        && substr($newContent, -1) !== '.'
54✔
1904
                        && substr(strtolower($newContent), -1) !== 'e'
54✔
1905
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
54✔
1906
                    ) {
27✔
1907
                        $newContent .= $tokens[$i][1];
54✔
1908

1909
                        // Support floats.
1910
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
54✔
1911
                            && ($tokens[($i + 1)] === '-'
54✔
1912
                            || $tokens[($i + 1)] === '+')
54✔
1913
                        ) {
27✔
1914
                            $newContent .= $tokens[($i + 1)];
54✔
1915
                            $i++;
54✔
1916
                        }
27✔
1917

1918
                        continue;
54✔
1919
                    }//end if
1920

1921
                    break;
54✔
1922
                }//end for
1923

1924
                if ($newType === T_LNUMBER
27✔
1925
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1926
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1927
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1928
                    || (stripos($newContent, '0x') !== 0
54✔
1929
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
54✔
1930
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
54✔
1931
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
54✔
1932
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
54✔
1933
                ) {
27✔
1934
                    $newType = T_DNUMBER;
54✔
1935
                }
27✔
1936

1937
                $newToken            = [];
54✔
1938
                $newToken['code']    = $newType;
54✔
1939
                $newToken['type']    = Tokens::tokenName($newType);
54✔
1940
                $newToken['content'] = $newContent;
54✔
1941
                $finalTokens[$newStackPtr] = $newToken;
54✔
1942

1943
                $newStackPtr++;
54✔
1944
                $stackPtr = ($i - 1);
54✔
1945
                continue;
54✔
1946
            }//end if
1947

1948
            /*
1949
                Backfill the T_MATCH token for PHP versions < 8.0 and
1950
                do initial correction for non-match expression T_MATCH tokens
1951
                to T_STRING for PHP >= 8.0.
1952
                A final check for non-match expression T_MATCH tokens is done
1953
                in PHP::processAdditional().
1954
            */
1955

1956
            if ($tokenIsArray === true
2,004✔
1957
                && (($token[0] === T_STRING
2,968✔
1958
                && strtolower($token[1]) === 'match')
2,858✔
1959
                || $token[0] === T_MATCH)
2,968✔
1960
            ) {
964✔
1961
                $isMatch = false;
750✔
1962
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
750✔
1963
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
750✔
1964
                        continue;
750✔
1965
                    }
1966

1967
                    if ($tokens[$x] !== '(') {
750✔
1968
                        // This is not a match expression.
1969
                        break;
529✔
1970
                    }
1971

1972
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
750✔
1973
                        // Also not a match expression.
1974
                        break;
183✔
1975
                    }
1976

1977
                    $isMatch = true;
750✔
1978
                    break;
750✔
1979
                }//end for
1980

1981
                if ($isMatch === true && $token[0] === T_STRING) {
750✔
1982
                    $newToken            = [];
500✔
1983
                    $newToken['code']    = T_MATCH;
500✔
1984
                    $newToken['type']    = 'T_MATCH';
500✔
1985
                    $newToken['content'] = $token[1];
500✔
1986

1987
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
500✔
1988
                        echo "\t\t* token $stackPtr changed from T_STRING to T_MATCH".PHP_EOL;
×
1989
                    }
1990

1991
                    $finalTokens[$newStackPtr] = $newToken;
500✔
1992
                    $newStackPtr++;
500✔
1993
                    continue;
500✔
1994
                } else if ($isMatch === false && $token[0] === T_MATCH) {
718✔
1995
                    // PHP 8.0, match keyword, but not a match expression.
1996
                    $newToken            = [];
61✔
1997
                    $newToken['code']    = T_STRING;
61✔
1998
                    $newToken['type']    = 'T_STRING';
61✔
1999
                    $newToken['content'] = $token[1];
61✔
2000

2001
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
61✔
2002
                        echo "\t\t* token $stackPtr changed from T_MATCH to T_STRING".PHP_EOL;
×
2003
                    }
2004

2005
                    $finalTokens[$newStackPtr] = $newToken;
61✔
2006
                    $newStackPtr++;
61✔
2007
                    continue;
61✔
2008
                }//end if
2009
            }//end if
234✔
2010

2011
            /*
2012
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
2013
                to prevent scope being set and the scope for switch default statements
2014
                breaking.
2015
            */
2016

2017
            if ($tokenIsArray === true
2,004✔
2018
                && $token[0] === T_DEFAULT
2,968✔
2019
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,968✔
2020
            ) {
964✔
2021
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,389✔
2022
                    if ($tokens[$x] === ',') {
1,389✔
2023
                        // Skip over potential trailing comma (supported in PHP).
2024
                        continue;
231✔
2025
                    }
2026

2027
                    if (is_array($tokens[$x]) === false
1,389✔
2028
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
1,389✔
2029
                    ) {
463✔
2030
                        // Non-empty, non-comma content.
2031
                        break;
1,389✔
2032
                    }
2033
                }
250✔
2034

2035
                if (isset($tokens[$x]) === true
1,389✔
2036
                    && is_array($tokens[$x]) === true
1,389✔
2037
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,389✔
2038
                ) {
463✔
2039
                    // Modify the original token stack for the double arrow so that
2040
                    // future checks can disregard the double arrow token more easily.
2041
                    // For match expression "case" statements, this is handled
2042
                    // in PHP::processAdditional().
2043
                    $tokens[$x][0] = T_MATCH_ARROW;
750✔
2044
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
750✔
2045
                        echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
2046
                    }
2047

2048
                    $newToken            = [];
750✔
2049
                    $newToken['code']    = T_MATCH_DEFAULT;
750✔
2050
                    $newToken['type']    = 'T_MATCH_DEFAULT';
750✔
2051
                    $newToken['content'] = $token[1];
750✔
2052

2053
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
750✔
2054
                        echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
×
2055
                    }
2056

2057
                    $finalTokens[$newStackPtr] = $newToken;
750✔
2058
                    $newStackPtr++;
750✔
2059
                    continue;
750✔
2060
                }//end if
2061
            }//end if
463✔
2062

2063
            /*
2064
                Convert ? to T_NULLABLE OR T_INLINE_THEN
2065
            */
2066

2067
            if ($tokenIsArray === false && $token[0] === '?') {
2,968✔
2068
                $newToken            = [];
1,162✔
2069
                $newToken['content'] = '?';
1,162✔
2070

2071
                // For typed constants, we only need to check the token before the ? to be sure.
2072
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,162✔
2073
                    $newToken['code'] = T_NULLABLE;
151✔
2074
                    $newToken['type'] = 'T_NULLABLE';
151✔
2075

2076
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
151✔
2077
                        echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2078
                    }
2079

2080
                    $finalTokens[$newStackPtr] = $newToken;
151✔
2081
                    $newStackPtr++;
151✔
2082
                    continue;
151✔
2083
                }
2084

2085
                /*
2086
                 * Check if the next non-empty token is one of the tokens which can be used
2087
                 * in type declarations. If not, it's definitely a ternary.
2088
                 * At this point, the only token types which need to be taken into consideration
2089
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
2090
                 */
2091

2092
                $lastRelevantNonEmpty = null;
1,162✔
2093

2094
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,162✔
2095
                    if (is_array($tokens[$i]) === true) {
1,162✔
2096
                        $tokenType = $tokens[$i][0];
1,162✔
2097
                    } else {
362✔
2098
                        $tokenType = $tokens[$i];
929✔
2099
                    }
2100

2101
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
1,162✔
2102
                        continue;
1,162✔
2103
                    }
2104

2105
                    if ($tokenType === T_STRING
800✔
2106
                        || $tokenType === T_NAME_FULLY_QUALIFIED
1,162✔
2107
                        || $tokenType === T_NAME_RELATIVE
1,162✔
2108
                        || $tokenType === T_NAME_QUALIFIED
1,162✔
2109
                        || $tokenType === T_ARRAY
1,162✔
2110
                        || $tokenType === T_NAMESPACE
1,162✔
2111
                        || $tokenType === T_NS_SEPARATOR
1,162✔
2112
                    ) {
362✔
2113
                        $lastRelevantNonEmpty = $tokenType;
929✔
2114
                        continue;
929✔
2115
                    }
2116

2117
                    if (($tokenType !== T_CALLABLE
800✔
2118
                        && isset($lastRelevantNonEmpty) === false)
1,162✔
2119
                        || ($lastRelevantNonEmpty === T_ARRAY
599✔
2120
                        && $tokenType === '(')
929✔
2121
                        || (($lastRelevantNonEmpty === T_STRING
1,060✔
2122
                        || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED
730✔
2123
                        || $lastRelevantNonEmpty === T_NAME_RELATIVE
400✔
2124
                        || $lastRelevantNonEmpty === T_NAME_QUALIFIED)
730✔
2125
                        && ($tokenType === T_DOUBLE_COLON
1,060✔
2126
                        || $tokenType === '('
1,060✔
2127
                        || $tokenType === ':'))
1,130✔
2128
                    ) {
362✔
2129
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,027✔
2130
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2131
                        }
2132

2133
                        $newToken['code'] = T_INLINE_THEN;
1,027✔
2134
                        $newToken['type'] = 'T_INLINE_THEN';
1,027✔
2135

2136
                        $insideInlineIf[] = $stackPtr;
1,027✔
2137

2138
                        $finalTokens[$newStackPtr] = $newToken;
1,027✔
2139
                        $newStackPtr++;
1,027✔
2140
                        continue 2;
1,027✔
2141
                    }
2142

2143
                    break;
135✔
2144
                }//end for
2145

2146
                /*
2147
                 * This can still be a nullable type or a ternary.
2148
                 * Do additional checking.
2149
                 */
2150

2151
                $prevNonEmpty     = null;
156✔
2152
                $lastSeenNonEmpty = null;
156✔
2153

2154
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
156✔
2155
                    if (is_array($tokens[$i]) === true) {
156✔
2156
                        $tokenType = $tokens[$i][0];
156✔
2157
                    } else {
52✔
2158
                        $tokenType = $tokens[$i];
156✔
2159
                    }
2160

2161
                    if ($tokenType === T_STATIC
104✔
2162
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
104✔
2163
                        || $lastSeenNonEmpty === '(')
104✔
2164
                    ) {
52✔
2165
                        $lastSeenNonEmpty = $tokenType;
×
2166
                        continue;
×
2167
                    }
2168

2169
                    if ($prevNonEmpty === null
104✔
2170
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
156✔
2171
                    ) {
52✔
2172
                        // Found the previous non-empty token.
2173
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
156✔
2174
                            $newToken['code'] = T_NULLABLE;
135✔
2175
                            $newToken['type'] = 'T_NULLABLE';
135✔
2176

2177
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2178
                                echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2179
                            }
2180

2181
                            break;
135✔
2182
                        }
2183

2184
                        $prevNonEmpty = $tokenType;
156✔
2185
                    }
52✔
2186

2187
                    if ($tokenType === T_FUNCTION
104✔
2188
                        || $tokenType === T_FN
156✔
2189
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
156✔
2190
                        || $tokenType === T_VAR
156✔
2191
                    ) {
52✔
2192
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2193
                            echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2194
                        }
2195

2196
                        $newToken['code'] = T_NULLABLE;
135✔
2197
                        $newToken['type'] = 'T_NULLABLE';
135✔
2198
                        break;
135✔
2199
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
156✔
2200
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
21✔
2201
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2202
                        }
2203

2204
                        $newToken['code'] = T_INLINE_THEN;
21✔
2205
                        $newToken['type'] = 'T_INLINE_THEN';
21✔
2206

2207
                        $insideInlineIf[] = $stackPtr;
21✔
2208
                        break;
21✔
2209
                    }
2210

2211
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
156✔
2212
                        $lastSeenNonEmpty = $tokenType;
156✔
2213
                    }
52✔
2214
                }//end for
52✔
2215

2216
                $finalTokens[$newStackPtr] = $newToken;
156✔
2217
                $newStackPtr++;
156✔
2218
                continue;
156✔
2219
            }//end if
2220

2221
            /*
2222
                Tokens after a double colon may look like scope openers,
2223
                such as when writing code like Foo::NAMESPACE, but they are
2224
                only ever variables or strings.
2225
            */
2226

2227
            if ($stackPtr > 1
2,004✔
2228
                && (is_array($tokens[($stackPtr - 1)]) === true
2,968✔
2229
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
2,968✔
2230
                && $tokenIsArray === true
2,968✔
2231
                && $token[0] !== T_STRING
2,968✔
2232
                && $token[0] !== T_VARIABLE
2,968✔
2233
                && $token[0] !== T_DOLLAR
2,968✔
2234
                && isset(Tokens::$emptyTokens[$token[0]]) === false
2,968✔
2235
            ) {
964✔
2236
                $newToken            = [];
×
2237
                $newToken['code']    = T_STRING;
×
2238
                $newToken['type']    = 'T_STRING';
×
2239
                $newToken['content'] = $token[1];
×
2240
                $finalTokens[$newStackPtr] = $newToken;
×
2241

2242
                $newStackPtr++;
×
2243
                continue;
×
2244
            }
2245

2246
            /*
2247
                Backfill the T_FN token for PHP versions < 7.4.
2248
            */
2249

2250
            if ($tokenIsArray === true
2,004✔
2251
                && $token[0] === T_STRING
2,968✔
2252
                && strtolower($token[1]) === 'fn'
2,968✔
2253
            ) {
964✔
2254
                // Modify the original token stack so that
2255
                // future checks (like looking for T_NULLABLE) can
2256
                // detect the T_FN token more easily.
2257
                $tokens[$stackPtr][0] = T_FN;
1,286✔
2258
                $token[0] = T_FN;
1,286✔
2259
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,286✔
2260
                    echo "\t\t* token $stackPtr changed from T_STRING to T_FN".PHP_EOL;
×
2261
                }
2262
            }
643✔
2263

2264
            /*
2265
                This is a special condition for T_ARRAY tokens used for
2266
                function return types. We want to keep the parenthesis map clean,
2267
                so let's tag these tokens as T_STRING.
2268
            */
2269

2270
            if ($tokenIsArray === true
2,004✔
2271
                && ($token[0] === T_FUNCTION
2,968✔
2272
                || $token[0] === T_FN)
2,968✔
2273
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
2,968✔
2274
            ) {
964✔
2275
                // Go looking for the colon to start the return type hint.
2276
                // Start by finding the closing parenthesis of the function.
2277
                $parenthesisStack  = [];
2,319✔
2278
                $parenthesisCloser = false;
2,319✔
2279
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,319✔
2280
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,319✔
2281
                        $parenthesisStack[] = $x;
2,319✔
2282
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,319✔
2283
                        array_pop($parenthesisStack);
2,319✔
2284
                        if (empty($parenthesisStack) === true) {
2,319✔
2285
                            $parenthesisCloser = $x;
2,319✔
2286
                            break;
2,319✔
2287
                        }
2288
                    }
196✔
2289
                }
773✔
2290

2291
                if ($parenthesisCloser !== false) {
2,319✔
2292
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,319✔
2293
                        if (is_array($tokens[$x]) === false
2,319✔
2294
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
2,319✔
2295
                        ) {
773✔
2296
                            // Non-empty content.
2297
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,319✔
2298
                                // Found a use statements, so search ahead for the closing parenthesis.
2299
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
2300
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
2301
                                        continue(2);
57✔
2302
                                    }
2303
                                }
19✔
2304
                            }
2305

2306
                            break;
2,319✔
2307
                        }
2308
                    }
752✔
2309

2310
                    if (isset($tokens[$x]) === true
2,319✔
2311
                        && is_array($tokens[$x]) === false
2,319✔
2312
                        && $tokens[$x] === ':'
2,319✔
2313
                    ) {
773✔
2314
                        // Find the start of the return type.
2315
                        for ($x += 1; $x < $numTokens; $x++) {
1,959✔
2316
                            if (is_array($tokens[$x]) === true
1,959✔
2317
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
1,959✔
2318
                            ) {
653✔
2319
                                // Whitespace or comments before the return type.
2320
                                continue;
1,959✔
2321
                            }
2322

2323
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1,959✔
2324
                                // Found a nullable operator, so skip it.
2325
                                // But also convert the token to save the tokenizer
2326
                                // a bit of time later on.
2327
                                $tokens[$x] = [
135✔
2328
                                    T_NULLABLE,
135✔
2329
                                    '?',
135✔
2330
                                ];
45✔
2331

2332
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
135✔
2333
                                    echo "\t\t* token $x changed from ? to T_NULLABLE".PHP_EOL;
×
2334
                                }
2335

2336
                                continue;
135✔
2337
                            }
2338

2339
                            break;
1,959✔
2340
                        }//end for
2341
                    }//end if
653✔
2342
                }//end if
773✔
2343
            }//end if
773✔
2344

2345
            /*
2346
                Before PHP 7, the <=> operator was tokenized as
2347
                T_IS_SMALLER_OR_EQUAL followed by T_GREATER_THAN.
2348
                So look for and combine these tokens in earlier versions.
2349
            */
2350

2351
            if ($tokenIsArray === true
2,004✔
2352
                && $token[0] === T_IS_SMALLER_OR_EQUAL
2,968✔
2353
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
2354
                && $tokens[($stackPtr + 1)][0] === '>'
2,968✔
2355
            ) {
964✔
2356
                $newToken            = [];
×
2357
                $newToken['code']    = T_SPACESHIP;
×
2358
                $newToken['type']    = 'T_SPACESHIP';
×
2359
                $newToken['content'] = '<=>';
×
2360
                $finalTokens[$newStackPtr] = $newToken;
×
2361

2362
                $newStackPtr++;
×
2363
                $stackPtr++;
×
2364
                continue;
×
2365
            }
2366

2367
            /*
2368
                PHP doesn't assign a token to goto labels, so we have to.
2369
                These are just string tokens with a single colon after them. Double
2370
                colons are already tokenized and so don't interfere with this check.
2371
                But we do have to account for CASE statements, that look just like
2372
                goto labels.
2373
            */
2374

2375
            if ($tokenIsArray === true
2,004✔
2376
                && $token[0] === T_STRING
2,968✔
2377
                && isset($tokens[($stackPtr + 1)]) === true
2,968✔
2378
                && $tokens[($stackPtr + 1)] === ':'
2,968✔
2379
                && (is_array($tokens[($stackPtr - 1)]) === false
2,422✔
2380
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
2,460✔
2381
            ) {
964✔
2382
                $stopTokens = [
456✔
2383
                    T_CASE               => true,
1,368✔
2384
                    T_SEMICOLON          => true,
1,368✔
2385
                    T_OPEN_TAG           => true,
1,368✔
2386
                    T_OPEN_CURLY_BRACKET => true,
1,368✔
2387
                    T_INLINE_THEN        => true,
1,368✔
2388
                    T_ENUM               => true,
1,368✔
2389
                ];
912✔
2390

2391
                for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,368✔
2392
                    if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,368✔
2393
                        break;
1,368✔
2394
                    }
2395
                }
456✔
2396

2397
                if ($finalTokens[$x]['code'] !== T_CASE
1,368✔
2398
                    && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,368✔
2399
                    && $finalTokens[$x]['code'] !== T_ENUM
1,368✔
2400
                ) {
456✔
2401
                    $finalTokens[$newStackPtr] = [
96✔
2402
                        'content' => $token[1].':',
96✔
2403
                        'code'    => T_GOTO_LABEL,
96✔
2404
                        'type'    => 'T_GOTO_LABEL',
96✔
2405
                    ];
32✔
2406

2407
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
96✔
2408
                        echo "\t\t* token $stackPtr changed from T_STRING to T_GOTO_LABEL".PHP_EOL;
×
2409
                        echo "\t\t* skipping T_COLON token ".($stackPtr + 1).PHP_EOL;
×
2410
                    }
2411

2412
                    $newStackPtr++;
96✔
2413
                    $stackPtr++;
96✔
2414
                    continue;
96✔
2415
                }
2416
            }//end if
456✔
2417

2418
            /*
2419
                If this token has newlines in its content, split each line up
2420
                and create a new token for each line. We do this so it's easier
2421
                to ascertain where errors occur on a line.
2422
                Note that $token[1] is the token's content.
2423
            */
2424

2425
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
2,968✔
2426
                $tokenLines = explode($this->eolChar, $token[1]);
2,968✔
2427
                $numLines   = count($tokenLines);
2,968✔
2428
                $newToken   = [
1,002✔
2429
                    'type'    => Tokens::tokenName($token[0]),
2,968✔
2430
                    'code'    => $token[0],
2,968✔
2431
                    'content' => '',
2,968✔
2432
                ];
1,966✔
2433

2434
                for ($i = 0; $i < $numLines; $i++) {
2,968✔
2435
                    $newToken['content'] = $tokenLines[$i];
2,968✔
2436
                    if ($i === ($numLines - 1)) {
2,968✔
2437
                        if ($tokenLines[$i] === '') {
2,968✔
2438
                            break;
2,968✔
2439
                        }
2440
                    } else {
889✔
2441
                        $newToken['content'] .= $this->eolChar;
2,968✔
2442
                    }
2443

2444
                    $finalTokens[$newStackPtr] = $newToken;
2,968✔
2445
                    $newStackPtr++;
2,968✔
2446
                }
964✔
2447
            } else {
964✔
2448
                // Some T_STRING tokens should remain that way due to their context.
2449
                if ($tokenIsArray === true && $token[0] === T_STRING) {
2,968✔
2450
                    $preserveTstring = false;
2,638✔
2451

2452
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2453
                    // but the constant name should not be.
2454
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,638✔
2455
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,562✔
2456
                        || $insideConstDeclaration === true
1,784✔
2457
                    ) {
854✔
2458
                        // Find the next non-empty token.
2459
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,659✔
2460
                            if (is_array($tokens[$i]) === true
1,659✔
2461
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,659✔
2462
                            ) {
552✔
2463
                                continue;
1,524✔
2464
                            }
2465

2466
                            break;
1,659✔
2467
                        }
2468

2469
                        if ($tokens[$i] === '=') {
1,659✔
2470
                            $preserveTstring        = true;
1,464✔
2471
                            $insideConstDeclaration = false;
1,529✔
2472
                        }
487✔
2473
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,638✔
2474
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,638✔
2475
                    ) {
854✔
2476
                        $preserveTstring = true;
2,524✔
2477

2478
                        // Special case for syntax like: return new self/new parent
2479
                        // where self/parent should not be a string.
2480
                        $tokenContentLower = strtolower($token[1]);
2,524✔
2481
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,524✔
2482
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,524✔
2483
                        ) {
816✔
2484
                            $preserveTstring = false;
1,600✔
2485
                        }
373✔
2486
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,624✔
2487
                        // Function names for functions declared to return by reference.
2488
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,056✔
2489
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,056✔
2490
                                continue;
480✔
2491
                            }
2492

2493
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,056✔
2494
                                $preserveTstring = true;
480✔
2495
                            }
160✔
2496

2497
                            break;
1,056✔
2498
                        }
2499
                    } else {
352✔
2500
                        // Keywords with special PHPCS token when used as a function call.
2501
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,617✔
2502
                            if (is_array($tokens[$i]) === true
2,617✔
2503
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,617✔
2504
                            ) {
847✔
2505
                                continue;
2,244✔
2506
                            }
2507

2508
                            if ($tokens[$i][0] === '(') {
2,617✔
2509
                                $preserveTstring = true;
2,184✔
2510
                            }
728✔
2511

2512
                            break;
2,617✔
2513
                        }
2514
                    }//end if
2515

2516
                    if ($preserveTstring === true) {
2,638✔
2517
                        $finalTokens[$newStackPtr] = [
2,527✔
2518
                            'code'    => T_STRING,
2,527✔
2519
                            'type'    => 'T_STRING',
2,527✔
2520
                            'content' => $token[1],
2,527✔
2521
                        ];
855✔
2522

2523
                        $newStackPtr++;
2,527✔
2524
                        continue;
2,527✔
2525
                    }
2526
                }//end if
846✔
2527

2528
                $newToken = null;
2,968✔
2529
                if ($tokenIsArray === false) {
2,968✔
2530
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
2,965✔
2531
                        $newToken = self::$resolveTokenCache[$token[0]];
2,965✔
2532
                    }
963✔
2533
                } else {
963✔
2534
                    $cacheKey = null;
2,968✔
2535
                    if ($token[0] === T_STRING) {
2,968✔
2536
                        $cacheKey = strtolower($token[1]);
2,614✔
2537
                    } else if ($token[0] !== T_CURLY_OPEN) {
2,968✔
2538
                        $cacheKey = $token[0];
2,968✔
2539
                    }
964✔
2540

2541
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
2,968✔
2542
                        $newToken            = self::$resolveTokenCache[$cacheKey];
2,968✔
2543
                        $newToken['content'] = $token[1];
2,968✔
2544
                    }
964✔
2545
                }
2546

2547
                if ($newToken === null) {
2,968✔
2548
                    $newToken = self::standardiseToken($token);
47✔
2549
                }
16✔
2550

2551
                // Convert colons that are actually the ELSE component of an
2552
                // inline IF statement.
2553
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
2,968✔
2554
                    $isInlineIf = true;
1,027✔
2555

2556
                    // Make sure this isn't a named parameter label.
2557
                    // Get the previous non-empty token.
2558
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,027✔
2559
                        if (is_array($tokens[$i]) === false
1,027✔
2560
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,027✔
2561
                        ) {
317✔
2562
                            break;
1,027✔
2563
                        }
2564
                    }
317✔
2565

2566
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,027✔
2567
                        $isInlineIf = false;
639✔
2568
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2569
                            echo "\t\t* token is parameter label, not T_INLINE_ELSE".PHP_EOL;
×
2570
                        }
2571
                    }
213✔
2572

2573
                    if ($isInlineIf === true) {
1,027✔
2574
                        // Make sure this isn't a return type separator.
2575
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,027✔
2576
                            if (is_array($tokens[$i]) === false
1,027✔
2577
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,027✔
2578
                                && $tokens[$i][0] !== T_COMMENT
1,027✔
2579
                                && $tokens[$i][0] !== T_WHITESPACE)
1,027✔
2580
                            ) {
317✔
2581
                                break;
1,027✔
2582
                            }
2583
                        }
317✔
2584

2585
                        if ($tokens[$i] === ')') {
1,027✔
2586
                            $parenCount = 1;
639✔
2587
                            for ($i--; $i > 0; $i--) {
639✔
2588
                                if ($tokens[$i] === '(') {
639✔
2589
                                    $parenCount--;
639✔
2590
                                    if ($parenCount === 0) {
639✔
2591
                                        break;
639✔
2592
                                    }
2593
                                } else if ($tokens[$i] === ')') {
639✔
2594
                                    $parenCount++;
×
2595
                                }
2596
                            }
213✔
2597

2598
                            // We've found the open parenthesis, so if the previous
2599
                            // non-empty token is FUNCTION or USE, this is a return type.
2600
                            // Note that we need to skip T_STRING tokens here as these
2601
                            // can be function names.
2602
                            for ($i--; $i > 0; $i--) {
639✔
2603
                                if (is_array($tokens[$i]) === false
639✔
2604
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
639✔
2605
                                    && $tokens[$i][0] !== T_COMMENT
639✔
2606
                                    && $tokens[$i][0] !== T_WHITESPACE
639✔
2607
                                    && $tokens[$i][0] !== T_STRING)
639✔
2608
                                ) {
213✔
2609
                                    break;
639✔
2610
                                }
2611
                            }
213✔
2612

2613
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
639✔
2614
                                $isInlineIf = false;
639✔
2615
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2616
                                    echo "\t\t* token is return type, not T_INLINE_ELSE".PHP_EOL;
×
2617
                                }
2618
                            }
213✔
2619
                        }//end if
213✔
2620
                    }//end if
317✔
2621

2622
                    // Check to see if this is a CASE or DEFAULT opener.
2623
                    if ($isInlineIf === true) {
1,027✔
2624
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,027✔
2625
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,027✔
2626
                            if (is_array($tokens[$i]) === true
1,027✔
2627
                                && ($tokens[$i][0] === T_CASE
1,027✔
2628
                                || $tokens[$i][0] === T_DEFAULT)
1,027✔
2629
                            ) {
317✔
2630
                                $isInlineIf = false;
×
2631
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2632
                                    echo "\t\t* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE".PHP_EOL;
×
2633
                                }
2634

2635
                                break;
×
2636
                            }
2637

2638
                            if (is_array($tokens[$i]) === false
1,027✔
2639
                                && ($tokens[$i] === ';'
1,027✔
2640
                                || $tokens[$i] === '{'
1,027✔
2641
                                || $tokens[$i] === '}')
1,027✔
2642
                            ) {
317✔
2643
                                break;
822✔
2644
                            }
2645
                        }//end for
317✔
2646
                    }//end if
317✔
2647

2648
                    if ($isInlineIf === true) {
1,027✔
2649
                        array_pop($insideInlineIf);
1,027✔
2650
                        $newToken['code'] = T_INLINE_ELSE;
1,027✔
2651
                        $newToken['type'] = 'T_INLINE_ELSE';
1,027✔
2652

2653
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,027✔
2654
                            echo "\t\t* token changed from T_COLON to T_INLINE_ELSE".PHP_EOL;
×
2655
                        }
2656
                    }
317✔
2657
                }//end if
317✔
2658

2659
                // This is a special condition for T_ARRAY tokens used for anything else
2660
                // but array declarations, like type hinting function arguments as
2661
                // being arrays.
2662
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2663
                // T_STRING.
2664
                if ($newToken['code'] === T_ARRAY) {
2,968✔
2665
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,489✔
2666
                        if (is_array($tokens[$i]) === false
1,489✔
2667
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,489✔
2668
                        ) {
471✔
2669
                            // Non-empty content.
2670
                            break;
1,489✔
2671
                        }
2672
                    }
394✔
2673

2674
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,489✔
2675
                        $newToken['code'] = T_STRING;
1,258✔
2676
                        $newToken['type'] = 'T_STRING';
1,258✔
2677
                    }
394✔
2678
                }
471✔
2679

2680
                // This is a special case when checking PHP 5.5+ code in PHP < 5.5
2681
                // where "finally" should be T_FINALLY instead of T_STRING.
2682
                if ($newToken['code'] === T_STRING
2,968✔
2683
                    && strtolower($newToken['content']) === 'finally'
2,968✔
2684
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CLOSE_CURLY_BRACKET
2,968✔
2685
                ) {
964✔
2686
                    $newToken['code'] = T_FINALLY;
179✔
2687
                    $newToken['type'] = 'T_FINALLY';
179✔
2688
                }
179✔
2689

2690
                // This is a special case for PHP 5.6 use function and use const
2691
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2692
                // and T_CONST.
2693
                if (($newToken['code'] === T_FUNCTION
2,968✔
2694
                    || $newToken['code'] === T_CONST)
2,968✔
2695
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
2,968✔
2696
                ) {
964✔
2697
                    $newToken['code'] = T_STRING;
135✔
2698
                    $newToken['type'] = 'T_STRING';
135✔
2699
                }
45✔
2700

2701
                // This is a special case for use groups in PHP 7+ where leaving
2702
                // the curly braces as their normal tokens would confuse
2703
                // the scope map and sniffs.
2704
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
2,968✔
2705
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
2,968✔
2706
                ) {
964✔
2707
                    $newToken['code'] = T_OPEN_USE_GROUP;
135✔
2708
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
135✔
2709
                    $insideUseGroup   = true;
135✔
2710
                }
45✔
2711

2712
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
2,968✔
2713
                    $newToken['code'] = T_CLOSE_USE_GROUP;
135✔
2714
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
135✔
2715
                    $insideUseGroup   = false;
135✔
2716
                }
45✔
2717

2718
                $finalTokens[$newStackPtr] = $newToken;
2,968✔
2719
                $newStackPtr++;
2,968✔
2720
            }//end if
2721
        }//end for
964✔
2722

2723
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,968✔
2724
            echo "\t*** END PHP TOKENIZING ***".PHP_EOL;
×
2725
        }
2726

2727
        return $finalTokens;
2,968✔
2728

2729
    }//end tokenize()
2730

2731

2732
    /**
2733
     * Performs additional processing after main tokenizing.
2734
     *
2735
     * This additional processing checks for CASE statements that are using curly
2736
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2737
     * into T_CLOSURE when they are not standard function definitions. It also
2738
     * detects short array syntax and converts those square brackets into new tokens.
2739
     * It also corrects some usage of the static and class keywords. It also
2740
     * assigns tokens to function return types.
2741
     *
2742
     * @return void
2743
     */
2744
    protected function processAdditional()
1,753✔
2745
    {
2746
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,753✔
2747
            echo "\t*** START ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
2748
        }
2749

2750
        $this->createAttributesNestingMap();
1,753✔
2751

2752
        $numTokens         = count($this->tokens);
1,753✔
2753
        $lastSeenTypeToken = $numTokens;
1,753✔
2754

2755
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,753✔
2756
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2757
            if (isset($this->tokens[$i]['scope_opener']) === true
1,753✔
2758
                && isset($this->tokens[$i]['scope_condition']) === false
1,753✔
2759
            ) {
559✔
2760
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2761
            }
2762

2763
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,753✔
2764
                /*
2765
                    Detect functions that are actually closures and
2766
                    assign them a different token.
2767
                */
2768

2769
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,512✔
2770
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,500✔
2771
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,500✔
2772
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,500✔
2773
                        ) {
500✔
2774
                            break;
1,500✔
2775
                        }
2776
                    }
500✔
2777

2778
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,500✔
2779
                        $this->tokens[$i]['code'] = T_CLOSURE;
1,032✔
2780
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
1,032✔
2781
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,032✔
2782
                            $line = $this->tokens[$i]['line'];
×
2783
                            echo "\t* token $i on line $line changed from T_FUNCTION to T_CLOSURE".PHP_EOL;
×
2784
                        }
2785

2786
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,032✔
2787
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
636✔
2788
                                continue;
×
2789
                            }
2790

2791
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
636✔
2792
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
636✔
2793
                                $type = $this->tokens[$x]['type'];
×
2794
                                echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2795
                            }
2796
                        }
212✔
2797
                    }
344✔
2798
                }//end if
500✔
2799

2800
                continue;
1,512✔
2801
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,753✔
2802
                /*
2803
                    Detect anonymous classes and assign them a different token.
2804
                */
2805

2806
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,516✔
2807
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,516✔
2808
                        break;
1,516✔
2809
                    }
2810
                }
480✔
2811

2812
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,516✔
2813
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,516✔
2814
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,498✔
2815
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,507✔
2816
                ) {
480✔
2817
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,015✔
2818
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,015✔
2819
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,015✔
2820
                        $line = $this->tokens[$i]['line'];
×
2821
                        echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL;
×
2822
                    }
2823

2824
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,015✔
2825
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,015✔
2826
                    ) {
313✔
2827
                        $closer = $this->tokens[$x]['parenthesis_closer'];
619✔
2828

2829
                        $this->tokens[$i]['parenthesis_opener']     = $x;
619✔
2830
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
619✔
2831
                        $this->tokens[$i]['parenthesis_owner']      = $i;
619✔
2832
                        $this->tokens[$x]['parenthesis_owner']      = $i;
619✔
2833
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
619✔
2834

2835
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
619✔
2836
                            $line = $this->tokens[$i]['line'];
×
2837
                            echo "\t\t* added parenthesis keys to T_ANON_CLASS token $i on line $line".PHP_EOL;
×
2838
                        }
2839
                    }
181✔
2840

2841
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,015✔
2842
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,015✔
2843
                            continue;
×
2844
                        }
2845

2846
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,015✔
2847
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,015✔
2848
                            $type = $this->tokens[$x]['type'];
×
2849
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2850
                        }
2851
                    }
313✔
2852
                }//end if
313✔
2853

2854
                continue;
1,516✔
2855
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,753✔
2856
                // Possible arrow function.
2857
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,116✔
2858
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,116✔
2859
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,116✔
2860
                    ) {
372✔
2861
                        // Non-whitespace content.
2862
                        break;
1,116✔
2863
                    }
2864
                }
338✔
2865

2866
                if (isset($this->tokens[$x]) === true
1,116✔
2867
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,116✔
2868
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
1,116✔
2869
                ) {
372✔
2870
                    $ignore  = Tokens::$emptyTokens;
1,113✔
2871
                    $ignore += [
371✔
2872
                        T_ARRAY                  => T_ARRAY,
1,113✔
2873
                        T_CALLABLE               => T_CALLABLE,
1,113✔
2874
                        T_COLON                  => T_COLON,
1,113✔
2875
                        T_NAMESPACE              => T_NAMESPACE,
1,113✔
2876
                        T_NS_SEPARATOR           => T_NS_SEPARATOR,
1,113✔
2877
                        T_NULL                   => T_NULL,
1,113✔
2878
                        T_TRUE                   => T_TRUE,
1,113✔
2879
                        T_FALSE                  => T_FALSE,
1,113✔
2880
                        T_NULLABLE               => T_NULLABLE,
1,113✔
2881
                        T_PARENT                 => T_PARENT,
1,113✔
2882
                        T_SELF                   => T_SELF,
1,113✔
2883
                        T_STATIC                 => T_STATIC,
1,113✔
2884
                        T_STRING                 => T_STRING,
1,113✔
2885
                        T_TYPE_UNION             => T_TYPE_UNION,
1,113✔
2886
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
1,113✔
2887
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
1,113✔
2888
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
1,113✔
2889
                    ];
371✔
2890

2891
                    $closer = $this->tokens[$x]['parenthesis_closer'];
1,113✔
2892
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1,113✔
2893
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1,113✔
2894
                            break;
1,113✔
2895
                        }
2896
                    }
371✔
2897

2898
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1,113✔
2899
                        $endTokens = [
371✔
2900
                            T_COLON                => true,
1,113✔
2901
                            T_COMMA                => true,
1,113✔
2902
                            T_SEMICOLON            => true,
1,113✔
2903
                            T_CLOSE_PARENTHESIS    => true,
1,113✔
2904
                            T_CLOSE_SQUARE_BRACKET => true,
1,113✔
2905
                            T_CLOSE_CURLY_BRACKET  => true,
1,113✔
2906
                            T_CLOSE_SHORT_ARRAY    => true,
1,113✔
2907
                            T_OPEN_TAG             => true,
1,113✔
2908
                            T_CLOSE_TAG            => true,
1,113✔
2909
                        ];
742✔
2910

2911
                        $inTernary    = false;
1,113✔
2912
                        $lastEndToken = null;
1,113✔
2913

2914
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1,113✔
2915
                            // Arrow function closer should never be shared with the closer of a match
2916
                            // control structure.
2917
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,113✔
2918
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,113✔
2919
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
1,113✔
2920
                            ) {
371✔
2921
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
423✔
2922
                                    // Match in return value of arrow function. Move on to the next token.
2923
                                    continue;
423✔
2924
                                }
2925

2926
                                // Arrow function as return value for the last match case without trailing comma.
2927
                                if ($lastEndToken !== null) {
423✔
2928
                                    $scopeCloser = $lastEndToken;
423✔
2929
                                    break;
423✔
2930
                                }
2931

2932
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2933
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2934
                                        $scopeCloser = $lastNonEmpty;
186✔
2935
                                        break 2;
186✔
2936
                                    }
2937
                                }
62✔
2938
                            }
2939

2940
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1,113✔
2941
                                if ($lastEndToken !== null
742✔
2942
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
981✔
2943
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
672✔
2944
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
849✔
2945
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
981✔
2946
                                ) {
371✔
2947
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2948
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2949
                                            $scopeCloser = $lastNonEmpty;
186✔
2950
                                            break;
186✔
2951
                                        }
2952
                                    }
62✔
2953
                                }
62✔
2954

2955
                                break;
1,113✔
2956
                            }
2957

2958
                            if ($inTernary === false
742✔
2959
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
1,113✔
2960
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
1,113✔
2961
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
1,113✔
2962
                            ) {
371✔
2963
                                // Found a nested arrow function that already has the closer set and is in
2964
                                // the same scope as us, so we can use its closer.
2965
                                break;
186✔
2966
                            }
2967

2968
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1,113✔
2969
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1,113✔
2970
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
1,113✔
2971
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
1,113✔
2972
                            ) {
371✔
2973
                                // We minus 1 here in case the closer can be shared with us.
2974
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
423✔
2975
                                continue;
423✔
2976
                            }
2977

2978
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1,113✔
2979
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
717✔
2980
                                $lastEndToken = $scopeCloser;
717✔
2981
                                continue;
717✔
2982
                            }
2983

2984
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1,113✔
2985
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
186✔
2986
                                $lastEndToken = $scopeCloser;
186✔
2987
                                continue;
186✔
2988
                            }
2989

2990
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1,113✔
2991
                                $inTernary = true;
186✔
2992
                                continue;
186✔
2993
                            }
2994

2995
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1,113✔
2996
                                if ($inTernary === false) {
186✔
2997
                                    break;
186✔
2998
                                }
2999

3000
                                $inTernary = false;
186✔
3001
                                continue;
186✔
3002
                            }
3003
                        }//end for
371✔
3004

3005
                        if ($scopeCloser !== $numTokens) {
1,113✔
3006
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,113✔
3007
                                $line = $this->tokens[$i]['line'];
×
3008
                                echo "\t=> token $i on line $line processed as arrow function".PHP_EOL;
×
3009
                                echo "\t\t* scope opener set to $arrow *".PHP_EOL;
×
3010
                                echo "\t\t* scope closer set to $scopeCloser *".PHP_EOL;
×
3011
                                echo "\t\t* parenthesis opener set to $x *".PHP_EOL;
×
3012
                                echo "\t\t* parenthesis closer set to $closer *".PHP_EOL;
×
3013
                            }
3014

3015
                            $this->tokens[$i]['code']            = T_FN;
1,113✔
3016
                            $this->tokens[$i]['type']            = 'T_FN';
1,113✔
3017
                            $this->tokens[$i]['scope_condition'] = $i;
1,113✔
3018
                            $this->tokens[$i]['scope_opener']    = $arrow;
1,113✔
3019
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
1,113✔
3020
                            $this->tokens[$i]['parenthesis_owner']  = $i;
1,113✔
3021
                            $this->tokens[$i]['parenthesis_opener'] = $x;
1,113✔
3022
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
1,113✔
3023

3024
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
1,113✔
3025
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1,113✔
3026

3027
                            $this->tokens[$arrow]['scope_condition']       = $i;
1,113✔
3028
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
1,113✔
3029
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
1,113✔
3030
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
1,113✔
3031
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
1,113✔
3032
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
1,113✔
3033

3034
                            $opener = $this->tokens[$i]['parenthesis_opener'];
1,113✔
3035
                            $closer = $this->tokens[$i]['parenthesis_closer'];
1,113✔
3036
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
1,113✔
3037
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
1,113✔
3038

3039
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,113✔
3040
                                $line = $this->tokens[$arrow]['line'];
×
3041
                                echo "\t\t* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW".PHP_EOL;
×
3042
                            }
3043
                        }//end if
371✔
3044
                    }//end if
371✔
3045
                }//end if
371✔
3046

3047
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
3048
                if (isset($this->tokens[$i]['scope_closer']) === false) {
1,116✔
3049
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
585✔
3050
                        $line = $this->tokens[$i]['line'];
×
3051
                        echo "\t=> token $i on line $line is not an arrow function".PHP_EOL;
×
3052
                        echo "\t\t* token changed from T_FN to T_STRING".PHP_EOL;
×
3053
                    }
3054

3055
                    $this->tokens[$i]['code'] = T_STRING;
585✔
3056
                    $this->tokens[$i]['type'] = 'T_STRING';
762✔
3057
                }
195✔
3058
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,753✔
3059
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,111✔
3060
                    continue;
99✔
3061
                }
3062

3063
                // Unless there is a variable or a bracket before this token,
3064
                // it is the start of an array being defined using the short syntax.
3065
                $isShortArray = false;
1,111✔
3066
                $allowed      = [
383✔
3067
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,111✔
3068
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,111✔
3069
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,111✔
3070
                    T_VARIABLE                 => T_VARIABLE,
1,111✔
3071
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,111✔
3072
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,111✔
3073
                    T_STRING                   => T_STRING,
1,111✔
3074
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,111✔
3075
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,111✔
3076
                ];
728✔
3077
                $allowed     += Tokens::$magicConstants;
1,111✔
3078

3079
                for ($x = ($i - 1); $x >= 0; $x--) {
1,111✔
3080
                    // If we hit a scope opener, the statement has ended
3081
                    // without finding anything, so it's probably an array
3082
                    // using PHP 7.1 short list syntax.
3083
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,111✔
3084
                        $isShortArray = true;
234✔
3085
                        break;
234✔
3086
                    }
3087

3088
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,111✔
3089
                        // Allow for control structures without braces.
3090
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,111✔
3091
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,111✔
3092
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
411✔
3093
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,111✔
3094
                        ) {
345✔
3095
                            $isShortArray = true;
1,111✔
3096
                        }
345✔
3097

3098
                        break;
1,111✔
3099
                    }
3100
                }//end for
345✔
3101

3102
                if ($isShortArray === true) {
1,111✔
3103
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,111✔
3104
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,111✔
3105

3106
                    $closer = $this->tokens[$i]['bracket_closer'];
1,111✔
3107
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,111✔
3108
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,111✔
3109
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,111✔
3110
                        $line = $this->tokens[$i]['line'];
×
3111
                        echo "\t* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY".PHP_EOL;
×
3112
                        $line = $this->tokens[$closer]['line'];
×
3113
                        echo "\t* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY".PHP_EOL;
×
3114
                    }
3115
                }
345✔
3116

3117
                continue;
1,111✔
3118
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,753✔
3119
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
771✔
3120
                    // Not a match expression after all.
3121
                    $this->tokens[$i]['code'] = T_STRING;
102✔
3122
                    $this->tokens[$i]['type'] = 'T_STRING';
102✔
3123

3124
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3125
                        echo "\t\t* token $i changed from T_MATCH to T_STRING".PHP_EOL;
×
3126
                    }
3127

3128
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
102✔
3129
                        $opener = $this->tokens[$i]['parenthesis_opener'];
102✔
3130
                        $closer = $this->tokens[$i]['parenthesis_closer'];
102✔
3131
                        unset(
34✔
3132
                            $this->tokens[$opener]['parenthesis_owner'],
102✔
3133
                            $this->tokens[$closer]['parenthesis_owner']
102✔
3134
                        );
34✔
3135
                        unset(
34✔
3136
                            $this->tokens[$i]['parenthesis_opener'],
102✔
3137
                            $this->tokens[$i]['parenthesis_closer'],
102✔
3138
                            $this->tokens[$i]['parenthesis_owner']
102✔
3139
                        );
34✔
3140

3141
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
102✔
3142
                            echo "\t\t* cleaned parenthesis of token $i *".PHP_EOL;
34✔
3143
                        }
3144
                    }
34✔
3145
                } else {
34✔
3146
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3147
                    $searchFor  = [
257✔
3148
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
771✔
3149
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
771✔
3150
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
771✔
3151
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
771✔
3152
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
771✔
3153
                    ];
514✔
3154
                    $searchFor += Tokens::$scopeOpeners;
771✔
3155

3156
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
771✔
3157
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
771✔
3158
                            continue;
771✔
3159
                        }
3160

3161
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
477✔
3162
                            $x = $this->tokens[$x]['scope_closer'];
291✔
3163
                            continue;
291✔
3164
                        }
3165

3166
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
477✔
3167
                            $x = $this->tokens[$x]['parenthesis_closer'];
477✔
3168
                            continue;
477✔
3169
                        }
3170

3171
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
477✔
3172
                            $x = $this->tokens[$x]['bracket_closer'];
291✔
3173
                            continue;
291✔
3174
                        }
3175

3176
                        // This must be a double arrow, but make sure anyhow.
3177
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
477✔
3178
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
477✔
3179
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
477✔
3180

3181
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
477✔
3182
                                echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
3183
                            }
3184
                        }
159✔
3185
                    }//end for
159✔
3186
                }//end if
3187

3188
                continue;
771✔
3189
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,753✔
3190
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,753✔
3191
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,753✔
3192
            ) {
559✔
3193
                if ($lastSeenTypeToken < $i) {
1,750✔
3194
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3195
                    // No need to do it again.
3196
                    continue;
828✔
3197
                }
3198

3199
                /*
3200
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3201
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3202
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3203

3204
                    All type related tokens will be converted in one go as soon as this section is hit.
3205
                */
3206

3207
                $allowed = [
596✔
3208
                    T_STRING       => T_STRING,
1,750✔
3209
                    T_CALLABLE     => T_CALLABLE,
1,750✔
3210
                    T_SELF         => T_SELF,
1,750✔
3211
                    T_PARENT       => T_PARENT,
1,750✔
3212
                    T_STATIC       => T_STATIC,
1,750✔
3213
                    T_FALSE        => T_FALSE,
1,750✔
3214
                    T_TRUE         => T_TRUE,
1,750✔
3215
                    T_NULL         => T_NULL,
1,750✔
3216
                    T_NAMESPACE    => T_NAMESPACE,
1,750✔
3217
                    T_NS_SEPARATOR => T_NS_SEPARATOR,
1,750✔
3218
                ];
1,154✔
3219

3220
                $suspectedType       = null;
1,750✔
3221
                $typeTokenCountAfter = 0;
1,750✔
3222

3223
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,750✔
3224
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,750✔
3225
                        continue;
1,750✔
3226
                    }
3227

3228
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,750✔
3229
                        ++$typeTokenCountAfter;
1,321✔
3230
                        continue;
1,321✔
3231
                    }
3232

3233
                    if (($typeTokenCountAfter > 0
1,192✔
3234
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,750✔
3235
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,750✔
3236
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,746✔
3237
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,746✔
3238
                    ) {
558✔
3239
                        // Skip past reference and variadic indicators for parameter types.
3240
                        continue;
891✔
3241
                    }
3242

3243
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,750✔
3244
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3245
                        $suspectedType = 'property or parameter';
1,182✔
3246
                        break;
1,182✔
3247
                    }
3248

3249
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,750✔
3250
                        // Possible arrow function.
3251
                        $suspectedType = 'return';
1,113✔
3252
                        break;
1,113✔
3253
                    }
3254

3255
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,750✔
3256
                        // Possible abstract method or interface method.
3257
                        $suspectedType = 'return';
1,570✔
3258
                        break;
1,570✔
3259
                    }
3260

3261
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,750✔
3262
                        && isset($this->tokens[$x]['scope_condition']) === true
1,750✔
3263
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,750✔
3264
                    ) {
558✔
3265
                        $suspectedType = 'return';
1,500✔
3266
                        break;
1,500✔
3267
                    }
3268

3269
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,645✔
3270
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3271
                        $suspectedType = 'constant';
1,000✔
3272
                        break;
1,000✔
3273
                    }
3274

3275
                    break;
1,645✔
3276
                }//end for
3277

3278
                if (($typeTokenCountAfter === 0
1,192✔
3279
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,750✔
3280
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,750✔
3281
                    || isset($suspectedType) === false
1,192✔
3282
                ) {
558✔
3283
                    // Definitely not a union, intersection or DNF type, move on.
3284
                    continue;
1,750✔
3285
                }
3286

3287
                if ($suspectedType === 'property or parameter') {
1,711✔
3288
                    unset($allowed[T_STATIC]);
1,182✔
3289
                }
394✔
3290

3291
                $typeTokenCountBefore = 0;
1,711✔
3292
                $typeOperators        = [$i];
1,711✔
3293
                $parenthesesCount     = 0;
1,711✔
3294
                $confirmed            = false;
1,711✔
3295
                $maybeNullable        = null;
1,711✔
3296

3297
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
1,711✔
3298
                    ++$parenthesesCount;
1,711✔
3299
                }
545✔
3300

3301
                for ($x = ($i - 1); $x >= 0; $x--) {
1,711✔
3302
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,711✔
3303
                        continue;
1,423✔
3304
                    }
3305

3306
                    if ($suspectedType === 'property or parameter'
1,166✔
3307
                        && $this->tokens[$x]['code'] === T_STRING
1,711✔
3308
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,711✔
3309
                    ) {
545✔
3310
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3311
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3312
                        $this->tokens[$x]['code'] = T_STATIC;
294✔
3313
                        $this->tokens[$x]['type'] = 'T_STATIC';
294✔
3314

3315
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
294✔
3316
                            $line = $this->tokens[$x]['line'];
×
3317
                            echo "\t* token $x on line $line changed back from T_STRING to T_STATIC".PHP_EOL;
×
3318
                        }
3319
                    }
98✔
3320

3321
                    if ($suspectedType === 'property or parameter'
1,166✔
3322
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,711✔
3323
                    ) {
545✔
3324
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3325
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3326
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
1,182✔
3327
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
1,182✔
3328
                        ) {
394✔
3329
                            $confirmed = true;
1,077✔
3330
                            break;
1,077✔
3331
                        } else {
3332
                            // This may still be an arrow function which hasn't been handled yet.
3333
                            for ($y = ($x - 1); $y > 0; $y--) {
1,182✔
3334
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
1,182✔
3335
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
1,182✔
3336
                                ) {
394✔
3337
                                    // Non-whitespace content.
3338
                                    break;
1,182✔
3339
                                }
3340
                            }
394✔
3341

3342
                            if ($this->tokens[$y]['code'] === T_FN) {
1,182✔
3343
                                $confirmed = true;
876✔
3344
                                break;
876✔
3345
                            }
3346
                        }
3347
                    }//end if
394✔
3348

3349
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,711✔
3350
                        ++$typeTokenCountBefore;
1,423✔
3351
                        continue;
1,423✔
3352
                    }
3353

3354
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3355
                    if (($typeTokenCountBefore > 0
1,166✔
3356
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,711✔
3357
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,615✔
3358
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,615✔
3359
                    ) {
545✔
3360
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
829✔
3361
                            $maybeNullable = $x;
294✔
3362
                        }
98✔
3363

3364
                        continue;
829✔
3365
                    }
3366

3367
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
1,711✔
3368
                        $typeOperators[] = $x;
1,216✔
3369
                        continue;
1,216✔
3370
                    }
3371

3372
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,711✔
3373
                        ++$parenthesesCount;
1,423✔
3374
                        $typeOperators[] = $x;
1,423✔
3375
                        continue;
1,423✔
3376
                    }
3377

3378
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,711✔
3379
                        // Make sure this is the colon for a return type.
3380
                        for ($y = ($x - 1); $y > 0; $y--) {
912✔
3381
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
912✔
3382
                                break;
912✔
3383
                            }
3384
                        }
304✔
3385

3386
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
912✔
3387
                            // Definitely not a union, intersection or DNF return type, move on.
3388
                            continue 2;
294✔
3389
                        }
3390

3391
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
912✔
3392
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
876✔
3393
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
488✔
3394
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
682✔
3395
                            ) {
292✔
3396
                                $confirmed = true;
876✔
3397
                            }
292✔
3398

3399
                            break;
876✔
3400
                        }
3401

3402
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3403
                        // Closure use tokens won't be parentheses owners until PHPCS 4.0.
3404
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
912✔
3405
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
876✔
3406
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
876✔
3407
                                    break;
876✔
3408
                                }
3409
                            }
230✔
3410

3411
                            if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
876✔
3412
                                $confirmed = true;
876✔
3413
                            }
292✔
3414
                        }
292✔
3415

3416
                        break;
912✔
3417
                    }//end if
3418

3419
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,711✔
3420
                        $confirmed = true;
865✔
3421
                        break;
865✔
3422
                    }
3423

3424
                    if ($suspectedType === 'property or parameter'
1,166✔
3425
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,460✔
3426
                        || $this->tokens[$x]['code'] === T_VAR
1,200✔
3427
                        || $this->tokens[$x]['code'] === T_STATIC
1,153✔
3428
                        || $this->tokens[$x]['code'] === T_READONLY)
1,451✔
3429
                    ) {
545✔
3430
                        // This will also confirm constructor property promotion parameters, but that's fine.
3431
                        $confirmed = true;
996✔
3432
                    }
332✔
3433

3434
                    break;
1,711✔
3435
                }//end for
3436

3437
                // Remember the last token we examined as part of the (non-)"type declaration".
3438
                $lastSeenTypeToken = $x;
1,711✔
3439

3440
                if ($confirmed === false
1,166✔
3441
                    && $suspectedType === 'property or parameter'
1,711✔
3442
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,711✔
3443
                ) {
545✔
3444
                    $parens = $this->tokens[$i]['nested_parenthesis'];
690✔
3445
                    $last   = end($parens);
690✔
3446

3447
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
690✔
3448
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
690✔
3449
                    ) {
230✔
3450
                        $confirmed = true;
690✔
3451
                    } else {
230✔
3452
                        // No parenthesis owner set, this may be an arrow function which has not yet
3453
                        // had additional processing done.
3454
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
204✔
3455
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
204✔
3456
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
204✔
3457
                                    continue;
204✔
3458
                                }
3459

3460
                                break;
204✔
3461
                            }
3462

3463
                            if ($this->tokens[$x]['code'] === T_FN) {
204✔
3464
                                for (--$x; $x >= 0; $x--) {
×
3465
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
3466
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3467
                                    ) {
3468
                                        continue;
×
3469
                                    }
3470

3471
                                    break;
×
3472
                                }
3473

3474
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3475
                                    $confirmed = true;
×
3476
                                }
3477
                            }
3478
                        }//end if
68✔
3479
                    }//end if
3480

3481
                    unset($parens, $last);
690✔
3482
                }//end if
230✔
3483

3484
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
1,711✔
3485
                    // Not a (valid) union, intersection or DNF type after all, move on.
3486
                    continue;
1,606✔
3487
                }
3488

3489
                foreach ($typeOperators as $x) {
1,285✔
3490
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,285✔
3491
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,180✔
3492
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,180✔
3493

3494
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,180✔
3495
                            $line = $this->tokens[$x]['line'];
×
3496
                            echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
406✔
3497
                        }
3498
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,285✔
3499
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,180✔
3500
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,180✔
3501

3502
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,180✔
3503
                            $line = $this->tokens[$x]['line'];
×
3504
                            echo "\t* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION".PHP_EOL;
406✔
3505
                        }
3506
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,285✔
3507
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,285✔
3508
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,285✔
3509

3510
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,285✔
3511
                            $line = $this->tokens[$x]['line'];
×
3512
                            echo "\t* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS".PHP_EOL;
441✔
3513
                        }
3514
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,285✔
3515
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,285✔
3516
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,285✔
3517

3518
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,285✔
3519
                            $line = $this->tokens[$x]['line'];
×
3520
                            echo "\t* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS".PHP_EOL;
×
3521
                        }
3522
                    }//end if
403✔
3523
                }//end foreach
403✔
3524

3525
                if (isset($maybeNullable) === true) {
1,285✔
3526
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
294✔
3527
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
294✔
3528

3529
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
294✔
3530
                        $line = $this->tokens[$maybeNullable]['line'];
×
3531
                        echo "\t* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE".PHP_EOL;
×
3532
                    }
3533
                }
98✔
3534

3535
                continue;
1,285✔
3536
            } else if ($this->tokens[$i]['code'] === T_STATIC) {
1,753✔
3537
                for ($x = ($i - 1); $x > 0; $x--) {
1,285✔
3538
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,285✔
3539
                        break;
1,285✔
3540
                    }
3541
                }
403✔
3542

3543
                if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
1,285✔
3544
                    $this->tokens[$i]['code'] = T_STRING;
×
3545
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3546

3547
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3548
                        $line = $this->tokens[$i]['line'];
×
3549
                        echo "\t* token $i on line $line changed from T_STATIC to T_STRING".PHP_EOL;
×
3550
                    }
3551
                }
3552

3553
                continue;
1,285✔
3554
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,753✔
3555
                || $this->tokens[$i]['code'] === T_FALSE
1,753✔
3556
                || $this->tokens[$i]['code'] === T_NULL
1,753✔
3557
            ) {
559✔
3558
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,564✔
3559
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,564✔
3560
                        // Non-whitespace content.
3561
                        break;
1,564✔
3562
                    }
3563
                }
435✔
3564

3565
                if ($x !== $numTokens
1,068✔
3566
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,564✔
3567
                ) {
496✔
3568
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3569
                        $line = $this->tokens[$i]['line'];
×
3570
                        $type = $this->tokens[$i]['type'];
×
3571
                        echo "\t* token $i on line $line changed from $type to T_STRING".PHP_EOL;
×
3572
                    }
3573

3574
                    $this->tokens[$i]['code'] = T_STRING;
×
3575
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3576
                }
3577
            }//end if
496✔
3578

3579
            if (($this->tokens[$i]['code'] !== T_CASE
1,753✔
3580
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,753✔
3581
                || isset($this->tokens[$i]['scope_opener']) === false
1,344✔
3582
            ) {
559✔
3583
                // Only interested in CASE and DEFAULT statements from here on in.
3584
                continue;
1,753✔
3585
            }
3586

3587
            $scopeOpener = $this->tokens[$i]['scope_opener'];
450✔
3588
            $scopeCloser = $this->tokens[$i]['scope_closer'];
450✔
3589

3590
            // If the first char after the opener is a curly brace
3591
            // and that brace has been ignored, it is actually
3592
            // opening this case statement and the opener and closer are
3593
            // probably set incorrectly.
3594
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
450✔
3595
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
450✔
3596
                    // Non-whitespace content.
3597
                    break;
450✔
3598
                }
3599
            }
150✔
3600

3601
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
450✔
3602
                // Special case for multiple CASE statements that share the same
3603
                // closer. Because we are going backwards through the file, this next
3604
                // CASE statement is already fixed, so just use its closer and don't
3605
                // worry about fixing anything.
3606
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3607
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3608
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3609
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3610
                    $newType = $this->tokens[$newCloser]['type'];
×
3611
                    $line    = $this->tokens[$i]['line'];
×
3612
                    echo "\t* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3613
                }
3614

3615
                continue;
×
3616
            }
3617

3618
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
450✔
3619
                || isset($this->tokens[$x]['scope_condition']) === true
450✔
3620
            ) {
150✔
3621
                // Not a CASE/DEFAULT with a curly brace opener.
3622
                continue;
450✔
3623
            }
3624

3625
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3626
            // not whatever it already is. The opener needs to be the opening curly
3627
            // brace so everything matches up.
3628
            $newCloser = $this->tokens[$x]['bracket_closer'];
54✔
3629
            foreach ([$i, $x, $newCloser] as $index) {
54✔
3630
                $this->tokens[$index]['scope_condition'] = $i;
54✔
3631
                $this->tokens[$index]['scope_opener']    = $x;
54✔
3632
                $this->tokens[$index]['scope_closer']    = $newCloser;
54✔
3633
            }
18✔
3634

3635
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
54✔
3636
                $line      = $this->tokens[$i]['line'];
×
3637
                $tokenType = $this->tokens[$i]['type'];
×
3638

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

3643
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3644
                $newType = $this->tokens[$newCloser]['type'];
×
3645
                echo "\t* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3646
            }
3647

3648
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
54✔
3649
                unset($this->tokens[$scopeOpener]['scope_condition']);
54✔
3650
                unset($this->tokens[$scopeOpener]['scope_opener']);
54✔
3651
                unset($this->tokens[$scopeOpener]['scope_closer']);
54✔
3652
            }
18✔
3653

3654
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
54✔
3655
                unset($this->tokens[$scopeCloser]['scope_condition']);
54✔
3656
                unset($this->tokens[$scopeCloser]['scope_opener']);
54✔
3657
                unset($this->tokens[$scopeCloser]['scope_closer']);
54✔
3658
            } else {
18✔
3659
                // We were using a shared closer. All tokens that were
3660
                // sharing this closer with us, except for the scope condition
3661
                // and it's opener, need to now point to the new closer.
3662
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3663
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3664
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3665
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3666
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3667
                    ) {
3668
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3669

3670
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3671
                            $line      = $this->tokens[$y]['line'];
×
3672
                            $tokenType = $this->tokens[$y]['type'];
×
3673
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3674
                            $newType   = $this->tokens[$newCloser]['type'];
×
3675
                            echo "\t\t* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3676
                        }
3677
                    }
3678
                }
3679
            }//end if
3680

3681
            unset($this->tokens[$x]['bracket_opener']);
54✔
3682
            unset($this->tokens[$x]['bracket_closer']);
54✔
3683
            unset($this->tokens[$newCloser]['bracket_opener']);
54✔
3684
            unset($this->tokens[$newCloser]['bracket_closer']);
54✔
3685
            $this->tokens[$scopeCloser]['conditions'][] = $i;
54✔
3686

3687
            // Now fix up all the tokens that think they are
3688
            // inside the CASE/DEFAULT statement when they are really outside.
3689
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
54✔
3690
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3691
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3692
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3693
                        unset($this->tokens[$x]['conditions'][$num]);
×
3694

3695
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3696
                            $type     = $this->tokens[$x]['type'];
×
3697
                            $oldConds = '';
×
3698
                            foreach ($oldConditions as $condition) {
×
3699
                                $oldConds .= Tokens::tokenName($condition).',';
×
3700
                            }
3701

3702
                            $oldConds = rtrim($oldConds, ',');
×
3703

3704
                            $newConds = '';
×
3705
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3706
                                $newConds .= Tokens::tokenName($condition).',';
×
3707
                            }
3708

3709
                            $newConds = rtrim($newConds, ',');
×
3710

3711
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
3712
                            echo "\t\t\t=> conditions changed from $oldConds to $newConds".PHP_EOL;
×
3713
                        }
3714

3715
                        break;
×
3716
                    }//end if
3717
                }//end foreach
3718
            }//end for
3719
        }//end for
18✔
3720

3721
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,753✔
3722
            echo "\t*** END ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
3723
        }
3724

3725
    }//end processAdditional()
1,156✔
3726

3727

3728
    /**
3729
     * Takes a token produced from <code>token_get_all()</code> and produces a
3730
     * more uniform token.
3731
     *
3732
     * @param string|array $token The token to convert.
3733
     *
3734
     * @return array The new token.
3735
     */
3736
    public static function standardiseToken($token)
3✔
3737
    {
3738
        if (isset($token[1]) === false) {
3✔
3739
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3✔
3740
                return self::$resolveTokenCache[$token[0]];
1✔
3741
            }
3742
        } else {
1✔
3743
            $cacheKey = null;
3✔
3744
            if ($token[0] === T_STRING) {
3✔
3745
                $cacheKey = strtolower($token[1]);
3✔
3746
            } else if ($token[0] !== T_CURLY_OPEN) {
3✔
3747
                $cacheKey = $token[0];
3✔
3748
            }
1✔
3749

3750
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3✔
3751
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3752
                $newToken['content'] = $token[1];
×
3753
                return $newToken;
×
3754
            }
3755
        }
3756

3757
        if (isset($token[1]) === false) {
3✔
3758
            return self::resolveSimpleToken($token[0]);
3✔
3759
        }
3760

3761
        if ($token[0] === T_STRING) {
3✔
3762
            switch ($cacheKey) {
1✔
3763
            case 'false':
3✔
3764
                $newToken['type'] = 'T_FALSE';
3✔
3765
                break;
3✔
3766
            case 'true':
3✔
3767
                $newToken['type'] = 'T_TRUE';
3✔
3768
                break;
3✔
3769
            case 'null':
3✔
3770
                $newToken['type'] = 'T_NULL';
3✔
3771
                break;
3✔
3772
            case 'self':
3✔
3773
                $newToken['type'] = 'T_SELF';
3✔
3774
                break;
3✔
3775
            case 'parent':
3✔
3776
                $newToken['type'] = 'T_PARENT';
3✔
3777
                break;
3✔
3778
            default:
1✔
3779
                $newToken['type'] = 'T_STRING';
3✔
3780
                break;
3✔
3781
            }
1✔
3782

3783
            $newToken['code'] = constant($newToken['type']);
3✔
3784

3785
            self::$resolveTokenCache[$cacheKey] = $newToken;
3✔
3786
        } else if ($token[0] === T_CURLY_OPEN) {
3✔
3787
            $newToken = [
3788
                'code' => T_OPEN_CURLY_BRACKET,
×
3789
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3790
            ];
3791
        } else {
3792
            $newToken = [
1✔
3793
                'code' => $token[0],
3✔
3794
                'type' => Tokens::tokenName($token[0]),
3✔
3795
            ];
2✔
3796

3797
            self::$resolveTokenCache[$token[0]] = $newToken;
3✔
3798
        }//end if
3799

3800
        $newToken['content'] = $token[1];
3✔
3801
        return $newToken;
3✔
3802

3803
    }//end standardiseToken()
3804

3805

3806
    /**
3807
     * Converts simple tokens into a format that conforms to complex tokens
3808
     * produced by token_get_all().
3809
     *
3810
     * Simple tokens are tokens that are not in array form when produced from
3811
     * token_get_all().
3812
     *
3813
     * @param string $token The simple token to convert.
3814
     *
3815
     * @return array The new token in array format.
3816
     */
3817
    public static function resolveSimpleToken($token)
3✔
3818
    {
3819
        $newToken = [];
3✔
3820

3821
        switch ($token) {
1✔
3822
        case '{':
3✔
3823
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
3✔
3824
            break;
3✔
3825
        case '}':
3✔
3826
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
3✔
3827
            break;
3✔
3828
        case '[':
3✔
3829
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
3✔
3830
            break;
3✔
3831
        case ']':
3✔
3832
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
3✔
3833
            break;
3✔
3834
        case '(':
3✔
3835
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
3✔
3836
            break;
3✔
3837
        case ')':
3✔
3838
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
3✔
3839
            break;
3✔
3840
        case ':':
3✔
3841
            $newToken['type'] = 'T_COLON';
3✔
3842
            break;
3✔
3843
        case '.':
3✔
3844
            $newToken['type'] = 'T_STRING_CONCAT';
3✔
3845
            break;
3✔
3846
        case ';':
3✔
3847
            $newToken['type'] = 'T_SEMICOLON';
3✔
3848
            break;
3✔
3849
        case '=':
3✔
3850
            $newToken['type'] = 'T_EQUAL';
3✔
3851
            break;
3✔
3852
        case '*':
3✔
3853
            $newToken['type'] = 'T_MULTIPLY';
3✔
3854
            break;
3✔
3855
        case '/':
3✔
3856
            $newToken['type'] = 'T_DIVIDE';
3✔
3857
            break;
3✔
3858
        case '+':
3✔
3859
            $newToken['type'] = 'T_PLUS';
3✔
3860
            break;
3✔
3861
        case '-':
3✔
3862
            $newToken['type'] = 'T_MINUS';
3✔
3863
            break;
3✔
3864
        case '%':
3✔
3865
            $newToken['type'] = 'T_MODULUS';
3✔
3866
            break;
3✔
3867
        case '^':
3✔
3868
            $newToken['type'] = 'T_BITWISE_XOR';
3✔
3869
            break;
3✔
3870
        case '&':
3✔
3871
            $newToken['type'] = 'T_BITWISE_AND';
2✔
3872
            break;
2✔
3873
        case '|':
3✔
3874
            $newToken['type'] = 'T_BITWISE_OR';
3✔
3875
            break;
3✔
3876
        case '~':
3✔
3877
            $newToken['type'] = 'T_BITWISE_NOT';
3✔
3878
            break;
3✔
3879
        case '<':
3✔
3880
            $newToken['type'] = 'T_LESS_THAN';
3✔
3881
            break;
3✔
3882
        case '>':
3✔
3883
            $newToken['type'] = 'T_GREATER_THAN';
3✔
3884
            break;
3✔
3885
        case '!':
3✔
3886
            $newToken['type'] = 'T_BOOLEAN_NOT';
3✔
3887
            break;
3✔
3888
        case ',':
3✔
3889
            $newToken['type'] = 'T_COMMA';
3✔
3890
            break;
3✔
3891
        case '@':
3✔
3892
            $newToken['type'] = 'T_ASPERAND';
3✔
3893
            break;
3✔
3894
        case '$':
3✔
3895
            $newToken['type'] = 'T_DOLLAR';
3✔
3896
            break;
3✔
3897
        case '`':
3✔
3898
            $newToken['type'] = 'T_BACKTICK';
3✔
3899
            break;
3✔
3900
        default:
3901
            $newToken['type'] = 'T_NONE';
×
3902
            break;
×
3903
        }//end switch
3904

3905
        $newToken['code']    = constant($newToken['type']);
3✔
3906
        $newToken['content'] = $token;
3✔
3907

3908
        self::$resolveTokenCache[$token] = $newToken;
3✔
3909
        return $newToken;
3✔
3910

3911
    }//end resolveSimpleToken()
3912

3913

3914
    /**
3915
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3916
     * Handle parenthesis balancing while searching for closing token
3917
     *
3918
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3919
     * @param int             $start        The starting position.
3920
     * @param string|string[] $openerTokens The opening character.
3921
     * @param string          $closerChar   The closing character.
3922
     *
3923
     * @return int|null The position of the closing token, if found. NULL otherwise.
3924
     */
3925
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3926
    {
3927
        $numTokens    = count($tokens);
51✔
3928
        $stack        = [0];
51✔
3929
        $closer       = null;
51✔
3930
        $openerTokens = (array) $openerTokens;
51✔
3931

3932
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3933
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3934
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3935
            ) {
17✔
3936
                $stack[] = $x;
51✔
3937
            } else if ($tokens[$x] === $closerChar) {
51✔
3938
                array_pop($stack);
51✔
3939
                if (empty($stack) === true) {
51✔
3940
                    $closer = $x;
51✔
3941
                    break;
51✔
3942
                }
3943
            }
17✔
3944
        }
17✔
3945

3946
        return $closer;
51✔
3947

3948
    }//end findCloser()
3949

3950

3951
    /**
3952
     * PHP 8 attributes parser for PHP < 8
3953
     * Handles single-line and multiline attributes.
3954
     *
3955
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3956
     * @param int   $stackPtr The current position in token array.
3957
     *
3958
     * @return array|null The array of parsed attribute tokens
3959
     */
3960
    private function parsePhpAttribute(array &$tokens, $stackPtr)
38✔
3961
    {
3962

3963
        $token = $tokens[$stackPtr];
38✔
3964

3965
        $commentBody = substr($token[1], 2);
38✔
3966
        $subTokens   = @token_get_all('<?php '.$commentBody);
38✔
3967

3968
        foreach ($subTokens as $i => $subToken) {
38✔
3969
            if (is_array($subToken) === true
38✔
3970
                && $subToken[0] === T_COMMENT
38✔
3971
                && strpos($subToken[1], '#[') === 0
38✔
3972
            ) {
19✔
3973
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
38✔
3974
                if ($reparsed !== null) {
38✔
3975
                    array_splice($subTokens, $i, 1, $reparsed);
38✔
3976
                } else {
19✔
3977
                    $subToken[0] = T_ATTRIBUTE;
×
3978
                }
3979
            }
19✔
3980
        }
19✔
3981

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

3984
        // Go looking for the close bracket.
3985
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
38✔
3986
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
38✔
3987
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
38✔
3988
                if (is_array($token) === true) {
38✔
3989
                    $commentBody .= $token[1];
38✔
3990
                } else {
19✔
3991
                    $commentBody .= $token;
38✔
3992
                }
3993
            }
19✔
3994

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

3998
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
38✔
3999
            if ($bracketCloser !== null) {
38✔
4000
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
38✔
4001
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
38✔
4002
            }
19✔
4003
        }
19✔
4004

4005
        if ($bracketCloser === null) {
38✔
4006
            return null;
38✔
4007
        }
4008

4009
        return $subTokens;
38✔
4010

4011
    }//end parsePhpAttribute()
4012

4013

4014
    /**
4015
     * Creates a map for the attributes tokens that surround other tokens.
4016
     *
4017
     * @return void
4018
     */
4019
    private function createAttributesNestingMap()
3✔
4020
    {
4021
        $map = [];
3✔
4022
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
4023
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
4024
                && $i === $this->tokens[$i]['attribute_opener']
3✔
4025
            ) {
1✔
4026
                if (empty($map) === false) {
3✔
4027
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4028
                }
1✔
4029

4030
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
4031
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
4032
                        = $this->tokens[$i]['attribute_closer'];
3✔
4033
                }
1✔
4034
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
4035
                && $i === $this->tokens[$i]['attribute_closer']
3✔
4036
            ) {
1✔
4037
                array_pop($map);
3✔
4038
                if (empty($map) === false) {
3✔
4039
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4040
                }
1✔
4041
            } else {
1✔
4042
                if (empty($map) === false) {
3✔
4043
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
4044
                }
1✔
4045
            }//end if
4046
        }//end for
1✔
4047

4048
    }//end createAttributesNestingMap()
2✔
4049

4050

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