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

PHPCSStandards / PHP_CodeSniffer / 14312206404

07 Apr 2025 02:43PM UTC coverage: 78.707% (-0.001%) from 78.708%
14312206404

Pull #904

github

web-flow
Merge 386f8fa91 into 99818768c
Pull Request #904: Tests/Tokenizer: use markers for the `testSwitchDefault()` test

24855 of 31579 relevant lines covered (78.71%)

201.06 hits per line

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

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

10
namespace PHP_CodeSniffer\Tokenizers;
11

12
use PHP_CodeSniffer\Util\Common;
13
use PHP_CodeSniffer\Util\Tokens;
14

15
class PHP extends Tokenizer
16
{
17

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

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

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

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

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

506

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

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

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

534
        $insideInlineIf         = [];
8,931✔
535
        $insideUseGroup         = false;
8,931✔
536
        $insideConstDeclaration = false;
8,931✔
537

538
        $commentTokenizer = new Comment();
8,931✔
539

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

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

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

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

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

569
            if ($newStackPtr > 0
6,030✔
570
                && isset(Tokens::$emptyTokens[$finalTokens[($newStackPtr - 1)]['code']]) === false
8,931✔
571
            ) {
2,901✔
572
                $lastNotEmptyToken = ($newStackPtr - 1);
8,931✔
573
            }
2,901✔
574

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

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

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

606
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
8,931✔
607
                echo PHP_EOL;
×
608
            }
609

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

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

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

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

635
            if ($tokenIsArray === true
6,030✔
636
                && isset(Tokens::$contextSensitiveKeywords[$token[0]]) === true
8,931✔
637
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
8,640✔
638
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
8,349✔
639
                || $insideConstDeclaration === true)
8,640✔
640
            ) {
2,901✔
641
                if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
7,116✔
642
                    $preserveKeyword = false;
7,056✔
643

644
                    // `new class`, and `new static` should be preserved.
645
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
7,056✔
646
                        && ($token[0] === T_CLASS
6,279✔
647
                        || $token[0] === T_STATIC)
6,357✔
648
                    ) {
2,241✔
649
                        $preserveKeyword = true;
4,449✔
650
                    }
1,407✔
651

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

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

669
                    // `new class extends` `new class implements` should be preserved
670
                    if (($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS)
7,056✔
671
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CLASS
7,056✔
672
                    ) {
2,241✔
673
                        $preserveKeyword = true;
1,530✔
674
                    }
510✔
675

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

683
                            if (isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true) {
2,064✔
684
                                continue;
1,530✔
685
                            }
686

687
                            if ($tokens[$i][0] === T_NS_SEPARATOR) {
1,554✔
688
                                $preserveKeyword = true;
1,554✔
689
                            }
720✔
690

691
                            break;
1,554✔
692
                        }
693
                    }
720✔
694
                }//end if
2,241✔
695

696
                // Types in typed constants should not be touched, but the constant name should be.
697
                if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
7,116✔
698
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
7,116✔
699
                    || $insideConstDeclaration === true
4,815✔
700
                ) {
2,301✔
701
                    $preserveKeyword = true;
6,459✔
702

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

711
                        break;
6,459✔
712
                    }
713

714
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
6,459✔
715
                        $preserveKeyword        = false;
2,244✔
716
                        $insideConstDeclaration = false;
2,244✔
717
                    }
675✔
718
                }//end if
2,004✔
719

720
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
7,116✔
721
                    $preserveKeyword = true;
1,974✔
722

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

728
                        if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,974✔
729
                            $preserveKeyword = false;
1,710✔
730
                        }
570✔
731

732
                        break;
1,974✔
733
                    }
734
                }
645✔
735

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

742
                    $finalTokens[$newStackPtr] = [
2,940✔
743
                        'code'    => T_STRING,
2,940✔
744
                        'type'    => 'T_STRING',
2,940✔
745
                        'content' => $token[1],
2,940✔
746
                    ];
1,074✔
747

748
                    $newStackPtr++;
2,940✔
749
                    continue;
2,940✔
750
                }
751
            }//end if
2,079✔
752

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

758
            if ($tokenIsArray === true && $token[0] === T_CONST) {
8,931✔
759
                $insideConstDeclaration = true;
7,230✔
760
            }
2,334✔
761

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

766
            if ($insideConstDeclaration === true
6,030✔
767
                && $tokenIsArray === false
8,931✔
768
                && ($token[0] === '=' || $token[0] === ';')
8,931✔
769
            ) {
2,901✔
770
                $insideConstDeclaration = false;
3,342✔
771
            }
1,284✔
772

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

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

780
            if ($tokenIsArray === true
6,030✔
781
                && $token[0] === T_STATIC
8,931✔
782
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
8,931✔
783
            ) {
2,901✔
784
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
6,042✔
785
                    if (is_array($tokens[$i]) === true
6,042✔
786
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
6,042✔
787
                    ) {
1,938✔
788
                        continue;
4,125✔
789
                    }
790

791
                    if ($tokens[$i][0] === '(') {
6,042✔
792
                        $finalTokens[$newStackPtr] = [
1,530✔
793
                            'code'    => T_STRING,
1,530✔
794
                            'type'    => 'T_STRING',
1,530✔
795
                            'content' => $token[1],
1,530✔
796
                        ];
510✔
797

798
                        $newStackPtr++;
1,530✔
799
                        continue 2;
1,530✔
800
                    }
801

802
                    break;
6,042✔
803
                }
804
            }//end if
1,938✔
805

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

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

820
                continue;
279✔
821
            }
822

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

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

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

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

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

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

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

887
                if (isset($matches[2]) === true && $matches[2] !== '') {
222✔
888
                    $type = 'T_LNUMBER';
60✔
889
                    if ($matches[2][0] === '_') {
60✔
890
                        $type = 'T_STRING';
60✔
891
                    }
30✔
892

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

901
                $stackPtr++;
222✔
902
                continue;
222✔
903
            }//end if
904

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

911
            if ($tokenIsArray === true
6,030✔
912
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
8,931✔
913
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
8,931✔
914
            ) {
2,901✔
915
                $finalTokens[$newStackPtr] = [
1,296✔
916
                    'code'    => T_BITWISE_AND,
1,296✔
917
                    'type'    => 'T_BITWISE_AND',
1,296✔
918
                    'content' => $token[1],
1,296✔
919
                ];
1,296✔
920
                $newStackPtr++;
1,296✔
921
                continue;
1,296✔
922
            }
923

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

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

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

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

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

972
                $stackPtr = $i;
315✔
973

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

980
                for ($j = 0; $j < $numLines; $j++) {
315✔
981
                    $newToken['content'] = $tokenLines[$j];
315✔
982
                    if ($j === ($numLines - 1)) {
315✔
983
                        if ($tokenLines[$j] === '') {
315✔
984
                            break;
225✔
985
                        }
986
                    } else {
105✔
987
                        $newToken['content'] .= $this->eolChar;
180✔
988
                    }
989

990
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
315✔
991
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
315✔
992
                    $finalTokens[$newStackPtr] = $newToken;
315✔
993
                    $newStackPtr++;
315✔
994
                }
105✔
995

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

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

1004
            if ($tokenIsArray === true
6,030✔
1005
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
8,931✔
1006
                && (substr($token[1], 0, 2) === 'b"'
8,616✔
1007
                || substr($token[1], 0, 2) === "b'")
8,616✔
1008
            ) {
2,901✔
1009
                $finalTokens[$newStackPtr] = [
×
1010
                    'code'    => T_BINARY_CAST,
×
1011
                    'type'    => 'T_BINARY_CAST',
×
1012
                    'content' => 'b',
×
1013
                ];
1014
                $newStackPtr++;
×
1015
                $token[1] = substr($token[1], 1);
×
1016
            }
1017

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

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

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

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

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

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

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

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

1085
                $stackPtr = $i;
396✔
1086
                $newStackPtr++;
396✔
1087

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

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

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

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

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

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

1124
                $newStackPtr++;
396✔
1125

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

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

1134
            if ($tokenIsArray === true
6,030✔
1135
                && $token[0] === T_STRING
8,931✔
1136
                && strtolower($token[1]) === 'enum'
8,931✔
1137
            ) {
2,901✔
1138
                // Get the next non-empty token.
1139
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
4,188✔
1140
                    if (is_array($tokens[$i]) === false
4,188✔
1141
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
4,188✔
1142
                    ) {
1,398✔
1143
                        break;
4,188✔
1144
                    }
1145
                }
759✔
1146

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

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

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

1164
                    $newStackPtr++;
1,632✔
1165
                    continue;
1,632✔
1166
                }
1167
            }//end if
1,278✔
1168

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

1173
            if ($tokenIsArray === true
6,030✔
1174
                && $token[0] === T_CASE
8,931✔
1175
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
8,931✔
1176
            ) {
2,901✔
1177
                $isEnumCase = false;
4,662✔
1178
                $scope      = 1;
4,662✔
1179

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

1186
                    if ($tokens[$i] === '{') {
4,662✔
1187
                        $scope--;
4,662✔
1188
                        continue;
4,662✔
1189
                    }
1190

1191
                    if (is_array($tokens[$i]) === false) {
4,662✔
1192
                        continue;
4,662✔
1193
                    }
1194

1195
                    if ($scope !== 0) {
4,662✔
1196
                        continue;
4,662✔
1197
                    }
1198

1199
                    if ($tokens[$i][0] === T_SWITCH) {
3,132✔
1200
                        break;
2,934✔
1201
                    }
1202

1203
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
3,132✔
1204
                        $isEnumCase = true;
387✔
1205
                        break;
387✔
1206
                    }
1207
                }//end for
1,044✔
1208

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

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

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

1223
                    $newStackPtr++;
387✔
1224
                    continue;
387✔
1225
                }
1226
            }//end if
1,488✔
1227

1228
            /*
1229
                As of PHP 8.0 fully qualified, partially qualified and namespace relative
1230
                identifier names are tokenized differently.
1231
                This "undoes" the new tokenization so the tokenization will be the same in
1232
                in PHP 5, 7 and 8.
1233
            */
1234

1235
            if (PHP_VERSION_ID >= 80000
8,931✔
1236
                && $tokenIsArray === true
8,931✔
1237
                && ($token[0] === T_NAME_QUALIFIED
5,916✔
1238
                || $token[0] === T_NAME_FULLY_QUALIFIED
3,015✔
1239
                || $token[0] === T_NAME_RELATIVE)
6,030✔
1240
            ) {
2,901✔
1241
                $name = $token[1];
1,866✔
1242

1243
                if ($token[0] === T_NAME_FULLY_QUALIFIED) {
1,866✔
1244
                    $newToken            = [];
1,062✔
1245
                    $newToken['code']    = T_NS_SEPARATOR;
1,062✔
1246
                    $newToken['type']    = 'T_NS_SEPARATOR';
1,062✔
1247
                    $newToken['content'] = '\\';
1,062✔
1248
                    $finalTokens[$newStackPtr] = $newToken;
1,062✔
1249
                    ++$newStackPtr;
1,062✔
1250

1251
                    $name = ltrim($name, '\\');
1,062✔
1252
                }
1253

1254
                if ($token[0] === T_NAME_RELATIVE) {
1,866✔
1255
                    $newToken            = [];
1,770✔
1256
                    $newToken['code']    = T_NAMESPACE;
1,770✔
1257
                    $newToken['type']    = 'T_NAMESPACE';
1,770✔
1258
                    $newToken['content'] = substr($name, 0, 9);
1,770✔
1259
                    $finalTokens[$newStackPtr] = $newToken;
1,770✔
1260
                    ++$newStackPtr;
1,770✔
1261

1262
                    $newToken            = [];
1,770✔
1263
                    $newToken['code']    = T_NS_SEPARATOR;
1,770✔
1264
                    $newToken['type']    = 'T_NS_SEPARATOR';
1,770✔
1265
                    $newToken['content'] = '\\';
1,770✔
1266
                    $finalTokens[$newStackPtr] = $newToken;
1,770✔
1267
                    ++$newStackPtr;
1,770✔
1268

1269
                    $name = substr($name, 10);
1,770✔
1270
                }
1271

1272
                $parts     = explode('\\', $name);
1,866✔
1273
                $partCount = count($parts);
1,866✔
1274
                $lastPart  = ($partCount - 1);
1,866✔
1275

1276
                foreach ($parts as $i => $part) {
1,866✔
1277
                    $newToken            = [];
1,866✔
1278
                    $newToken['code']    = T_STRING;
1,866✔
1279
                    $newToken['type']    = 'T_STRING';
1,866✔
1280
                    $newToken['content'] = $part;
1,866✔
1281
                    $finalTokens[$newStackPtr] = $newToken;
1,866✔
1282
                    ++$newStackPtr;
1,866✔
1283

1284
                    if ($i !== $lastPart) {
1,866✔
1285
                        $newToken            = [];
1,356✔
1286
                        $newToken['code']    = T_NS_SEPARATOR;
1,356✔
1287
                        $newToken['type']    = 'T_NS_SEPARATOR';
1,356✔
1288
                        $newToken['content'] = '\\';
1,356✔
1289
                        $finalTokens[$newStackPtr] = $newToken;
1,356✔
1290
                        ++$newStackPtr;
1,356✔
1291
                    }
1292
                }
1293

1294
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,866✔
1295
                    $type    = Tokens::tokenName($token[0]);
×
1296
                    $content = Common::prepareForOutput($token[1]);
×
1297
                    echo "\t\t* token $stackPtr split into individual tokens; was: $type => $content".PHP_EOL;
×
1298
                }
1299

1300
                continue;
1,866✔
1301
            }//end if
1302

1303
            /*
1304
                PHP 8.0 Attributes
1305
            */
1306

1307
            if (PHP_VERSION_ID < 80000
8,931✔
1308
                && $token[0] === T_COMMENT
8,931✔
1309
                && strpos($token[1], '#[') === 0
8,931✔
1310
            ) {
2,901✔
1311
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
102✔
1312
                if ($subTokens !== null) {
102✔
1313
                    array_splice($tokens, $stackPtr, 1, $subTokens);
102✔
1314
                    $numTokens = count($tokens);
102✔
1315

1316
                    $tokenIsArray = true;
102✔
1317
                    $token        = $tokens[$stackPtr];
102✔
1318
                } else {
51✔
1319
                    $token[0] = T_ATTRIBUTE;
102✔
1320
                }
1321
            }
51✔
1322

1323
            if ($tokenIsArray === true
6,030✔
1324
                && $token[0] === T_ATTRIBUTE
8,931✔
1325
            ) {
2,901✔
1326
                // Go looking for the close bracket.
1327
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
153✔
1328

1329
                $newToken            = [];
153✔
1330
                $newToken['code']    = T_ATTRIBUTE;
153✔
1331
                $newToken['type']    = 'T_ATTRIBUTE';
153✔
1332
                $newToken['content'] = '#[';
153✔
1333
                $finalTokens[$newStackPtr] = $newToken;
153✔
1334

1335
                $tokens[$bracketCloser]    = [];
153✔
1336
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
153✔
1337
                $tokens[$bracketCloser][1] = ']';
153✔
1338

1339
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
153✔
1340
                    echo "\t\t* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END".PHP_EOL;
×
1341
                }
1342

1343
                $newStackPtr++;
153✔
1344
                continue;
153✔
1345
            }//end if
1346

1347
            /*
1348
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1349
                token and ensures that the colon after it is always T_COLON.
1350
            */
1351

1352
            if ($tokenIsArray === true
6,030✔
1353
                && ($token[0] === T_STRING
8,931✔
1354
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
8,931✔
1355
            ) {
2,901✔
1356
                // Get the next non-empty token.
1357
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
8,400✔
1358
                    if (is_array($tokens[$i]) === false
8,400✔
1359
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
8,400✔
1360
                    ) {
2,724✔
1361
                        break;
8,400✔
1362
                    }
1363
                }
2,610✔
1364

1365
                if (isset($tokens[$i]) === true
8,400✔
1366
                    && is_array($tokens[$i]) === false
8,400✔
1367
                    && $tokens[$i] === ':'
8,400✔
1368
                ) {
2,724✔
1369
                    // Get the previous non-empty token.
1370
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
4,878✔
1371
                        if (is_array($tokens[$j]) === false
4,878✔
1372
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
4,878✔
1373
                        ) {
1,626✔
1374
                            break;
4,878✔
1375
                        }
1376
                    }
1,605✔
1377

1378
                    if (is_array($tokens[$j]) === false
4,878✔
1379
                        && ($tokens[$j] === '('
4,728✔
1380
                        || $tokens[$j] === ',')
4,728✔
1381
                    ) {
1,626✔
1382
                        $newToken            = [];
2,070✔
1383
                        $newToken['code']    = T_PARAM_NAME;
2,070✔
1384
                        $newToken['type']    = 'T_PARAM_NAME';
2,070✔
1385
                        $newToken['content'] = $token[1];
2,070✔
1386
                        $finalTokens[$newStackPtr] = $newToken;
2,070✔
1387

1388
                        $newStackPtr++;
2,070✔
1389

1390
                        // Modify the original token stack so that future checks, like
1391
                        // determining T_COLON vs T_INLINE_ELSE can handle this correctly.
1392
                        $tokens[$stackPtr][0] = T_PARAM_NAME;
2,070✔
1393

1394
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,070✔
1395
                            $type = Tokens::tokenName($token[0]);
×
1396
                            echo "\t\t* token $stackPtr changed from $type to T_PARAM_NAME".PHP_EOL;
×
1397
                        }
1398

1399
                        continue;
2,070✔
1400
                    }
1401
                }//end if
1,575✔
1402
            }//end if
2,724✔
1403

1404
            /*
1405
                "readonly" keyword for PHP < 8.1
1406
            */
1407

1408
            if ($tokenIsArray === true
6,030✔
1409
                && strtolower($token[1]) === 'readonly'
8,931✔
1410
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
6,912✔
1411
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
7,026✔
1412
            ) {
2,901✔
1413
                // Get the next non-whitespace token.
1414
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,988✔
1415
                    if (is_array($tokens[$i]) === false
2,988✔
1416
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
2,988✔
1417
                    ) {
996✔
1418
                        break;
2,988✔
1419
                    }
1420
                }
996✔
1421

1422
                $isReadonlyKeyword = false;
2,988✔
1423

1424
                if (isset($tokens[$i]) === false
2,988✔
1425
                    || $tokens[$i] !== '('
2,988✔
1426
                ) {
996✔
1427
                    $isReadonlyKeyword = true;
2,988✔
1428
                } else if ($tokens[$i] === '(') {
996✔
1429
                    /*
1430
                     * Skip over tokens which can be used in type declarations.
1431
                     * At this point, the only token types which need to be taken into consideration
1432
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1433
                     * and the union/intersection/dnf parentheses.
1434
                     */
1435

1436
                    $foundDNFParens = 1;
×
1437
                    $foundDNFPipe   = 0;
×
1438

1439
                    for (++$i; $i < $numTokens; $i++) {
×
1440
                        if (is_array($tokens[$i]) === true) {
×
1441
                            $tokenType = $tokens[$i][0];
×
1442
                        } else {
1443
                            $tokenType = $tokens[$i];
×
1444
                        }
1445

1446
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1447
                            continue;
×
1448
                        }
1449

1450
                        if ($tokenType === '|') {
×
1451
                            ++$foundDNFPipe;
×
1452
                            continue;
×
1453
                        }
1454

1455
                        if ($tokenType === ')') {
×
1456
                            ++$foundDNFParens;
×
1457
                            continue;
×
1458
                        }
1459

1460
                        if ($tokenType === '(') {
×
1461
                            ++$foundDNFParens;
×
1462
                            continue;
×
1463
                        }
1464

1465
                        if ($tokenType === T_STRING
1466
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1467
                            || $tokenType === T_NAME_RELATIVE
×
1468
                            || $tokenType === T_NAME_QUALIFIED
×
1469
                            || $tokenType === T_ARRAY
×
1470
                            || $tokenType === T_NAMESPACE
×
1471
                            || $tokenType === T_NS_SEPARATOR
×
1472
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1473
                            || $tokenType === '&' // PHP < 8.0.
×
1474
                        ) {
1475
                            continue;
×
1476
                        }
1477

1478
                        // Reached the next token after.
1479
                        if (($foundDNFParens % 2) === 0
×
1480
                            && $foundDNFPipe >= 1
×
1481
                            && ($tokenType === T_VARIABLE
×
1482
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1483
                        ) {
1484
                            $isReadonlyKeyword = true;
×
1485
                        }
1486

1487
                        break;
×
1488
                    }//end for
1489
                }//end if
1490

1491
                if ($isReadonlyKeyword === true) {
2,988✔
1492
                    $finalTokens[$newStackPtr] = [
2,988✔
1493
                        'code'    => T_READONLY,
2,988✔
1494
                        'type'    => 'T_READONLY',
2,988✔
1495
                        'content' => $token[1],
2,988✔
1496
                    ];
996✔
1497
                    $newStackPtr++;
2,988✔
1498

1499
                    // Also modify the original token stack so that
1500
                    // future checks (like looking for T_NULLABLE) can
1501
                    // detect the T_READONLY token more easily.
1502
                    $tokens[$stackPtr][0] = T_READONLY;
2,988✔
1503
                    $token[0] = T_READONLY;
2,988✔
1504

1505
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
2,988✔
1506
                        echo "\t\t* token $stackPtr changed from $type to T_READONLY".PHP_EOL;
996✔
1507
                    }
1508
                } else {
996✔
1509
                    $finalTokens[$newStackPtr] = [
×
1510
                        'code'    => T_STRING,
×
1511
                        'type'    => 'T_STRING',
×
1512
                        'content' => $token[1],
×
1513
                    ];
1514
                    $newStackPtr++;
×
1515

1516
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1517
                        echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
×
1518
                    }
1519
                }//end if
1520

1521
                continue;
2,988✔
1522
            }//end if
1523

1524
            /*
1525
                Before PHP 7.0, "yield from" was tokenized as
1526
                T_YIELD, T_WHITESPACE and T_STRING. So look for
1527
                and change this token in earlier versions.
1528
            */
1529

1530
            if (PHP_VERSION_ID < 70000
8,931✔
1531
                && $tokenIsArray === true
8,931✔
1532
                && $token[0] === T_YIELD
8,931✔
1533
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1534
                && isset($tokens[($stackPtr + 2)]) === true
8,931✔
1535
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
8,931✔
1536
                && strpos($tokens[($stackPtr + 1)][1], $this->eolChar) === false
8,931✔
1537
                && $tokens[($stackPtr + 2)][0] === T_STRING
8,931✔
1538
                && strtolower($tokens[($stackPtr + 2)][1]) === 'from'
8,931✔
1539
            ) {
2,901✔
1540
                // Single-line "yield from" with only whitespace between.
1541
                $finalTokens[$newStackPtr] = [
570✔
1542
                    'code'    => T_YIELD_FROM,
570✔
1543
                    'type'    => 'T_YIELD_FROM',
570✔
1544
                    'content' => $token[1].$tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1],
570✔
1545
                ];
1546

1547
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
570✔
1548
                    for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) {
×
1549
                        $type    = Tokens::tokenName($tokens[$i][0]);
×
1550
                        $content = Common::prepareForOutput($tokens[$i][1]);
×
1551
                        echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL;
×
1552
                    }
1553
                }
1554

1555
                $newStackPtr++;
570✔
1556
                $stackPtr += 2;
570✔
1557

1558
                continue;
570✔
1559
            } else if (PHP_VERSION_ID < 80300
8,931✔
1560
                && $tokenIsArray === true
8,931✔
1561
                && $token[0] === T_STRING
8,931✔
1562
                && strtolower($token[1]) === 'from'
8,931✔
1563
                && $finalTokens[$lastNotEmptyToken]['code'] === T_YIELD
8,931✔
1564
            ) {
2,901✔
1565
                /*
1566
                    Before PHP 8.3, if there was a comment between the "yield" and "from" keywords,
1567
                    it was tokenized as T_YIELD, T_WHITESPACE, T_COMMENT... and T_STRING.
1568
                    We want to keep the tokenization of the tokens between, but need to change the
1569
                    `T_YIELD` and `T_STRING` (from) keywords to `T_YIELD_FROM.
1570
                */
1571

1572
                $finalTokens[$lastNotEmptyToken]['code'] = T_YIELD_FROM;
120✔
1573
                $finalTokens[$lastNotEmptyToken]['type'] = 'T_YIELD_FROM';
120✔
1574

1575
                $finalTokens[$newStackPtr] = [
120✔
1576
                    'code'    => T_YIELD_FROM,
120✔
1577
                    'type'    => 'T_YIELD_FROM',
120✔
1578
                    'content' => $token[1],
120✔
1579
                ];
1580
                $newStackPtr++;
120✔
1581

1582
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
120✔
1583
                    echo "\t\t* token $lastNotEmptyToken (new stack) changed into T_YIELD_FROM; was: T_YIELD".PHP_EOL;
×
1584
                    echo "\t\t* token $stackPtr changed into T_YIELD_FROM; was: T_STRING".PHP_EOL;
×
1585
                }
1586

1587
                continue;
120✔
1588
            } else if (PHP_VERSION_ID >= 70000
8,931✔
1589
                && $tokenIsArray === true
8,931✔
1590
                && $token[0] === T_YIELD_FROM
8,931✔
1591
                && strpos($token[1], $this->eolChar) !== false
8,931✔
1592
                && preg_match('`^yield\s+from$`i', $token[1]) === 1
8,931✔
1593
            ) {
2,901✔
1594
                /*
1595
                    In PHP 7.0+, a multi-line "yield from" (without comment) tokenizes as a single
1596
                    T_YIELD_FROM token, but we want to split it and tokenize the whitespace
1597
                    separately for consistency.
1598
                */
1599

1600
                $finalTokens[$newStackPtr] = [
120✔
1601
                    'code'    => T_YIELD_FROM,
120✔
1602
                    'type'    => 'T_YIELD_FROM',
120✔
1603
                    'content' => substr($token[1], 0, 5),
120✔
1604
                ];
60✔
1605
                $newStackPtr++;
120✔
1606

1607
                $tokenLines = explode($this->eolChar, substr($token[1], 5, -4));
120✔
1608
                $numLines   = count($tokenLines);
120✔
1609
                $newToken   = [
60✔
1610
                    'type'    => 'T_WHITESPACE',
120✔
1611
                    'code'    => T_WHITESPACE,
120✔
1612
                    'content' => '',
120✔
1613
                ];
60✔
1614

1615
                foreach ($tokenLines as $i => $line) {
120✔
1616
                    $newToken['content'] = $line;
120✔
1617
                    if ($i === ($numLines - 1)) {
120✔
1618
                        if ($line === '') {
120✔
1619
                            break;
60✔
1620
                        }
1621
                    } else {
1622
                        $newToken['content'] .= $this->eolChar;
120✔
1623
                    }
1624

1625
                    $finalTokens[$newStackPtr] = $newToken;
120✔
1626
                    $newStackPtr++;
120✔
1627
                }
1628

1629
                $finalTokens[$newStackPtr] = [
120✔
1630
                    'code'    => T_YIELD_FROM,
120✔
1631
                    'type'    => 'T_YIELD_FROM',
120✔
1632
                    'content' => substr($token[1], -4),
120✔
1633
                ];
60✔
1634
                $newStackPtr++;
120✔
1635

1636
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
120✔
1637
                    echo "\t\t* token $stackPtr split into 'yield', one or more whitespace tokens and 'from'".PHP_EOL;
×
1638
                }
1639

1640
                continue;
120✔
1641
            } else if (PHP_VERSION_ID >= 80300
8,931✔
1642
                && $tokenIsArray === true
8,931✔
1643
                && $token[0] === T_YIELD_FROM
8,931✔
1644
                && preg_match('`^yield[ \t]+from$`i', $token[1]) !== 1
8,931✔
1645
                && stripos($token[1], 'yield') === 0
8,931✔
1646
            ) {
2,901✔
1647
                /*
1648
                    Since PHP 8.3, "yield from" allows for comments and will
1649
                    swallow the comment in the `T_YIELD_FROM` token.
1650
                    We need to split this up to allow for sniffs handling comments.
1651
                */
1652

1653
                $finalTokens[$newStackPtr] = [
60✔
1654
                    'code'    => T_YIELD_FROM,
60✔
1655
                    'type'    => 'T_YIELD_FROM',
60✔
1656
                    'content' => substr($token[1], 0, 5),
60✔
1657
                ];
60✔
1658
                $newStackPtr++;
60✔
1659

1660
                $yieldFromSubtokens = @token_get_all("<?php\n".substr($token[1], 5, -4));
60✔
1661
                // Remove the PHP open tag token.
1662
                array_shift($yieldFromSubtokens);
60✔
1663
                // Add the "from" keyword.
1664
                $yieldFromSubtokens[] = [
60✔
1665
                    0 => T_YIELD_FROM,
60✔
1666
                    1 => substr($token[1], -4),
60✔
1667
                ];
60✔
1668

1669
                // Inject the new tokens into the token stack.
1670
                array_splice($tokens, ($stackPtr + 1), 0, $yieldFromSubtokens);
60✔
1671
                $numTokens = count($tokens);
60✔
1672

1673
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
60✔
1674
                    echo "\t\t* token $stackPtr split into parts (yield from with comment)".PHP_EOL;
×
1675
                }
1676

1677
                unset($yieldFromSubtokens);
60✔
1678
                continue;
60✔
1679
            }//end if
1680

1681
            /*
1682
                Before PHP 5.6, the ... operator was tokenized as three
1683
                T_STRING_CONCAT tokens in a row. So look for and combine
1684
                these tokens in earlier versions.
1685
            */
1686

1687
            if ($tokenIsArray === false
6,030✔
1688
                && $token[0] === '.'
8,931✔
1689
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1690
                && isset($tokens[($stackPtr + 2)]) === true
8,931✔
1691
                && $tokens[($stackPtr + 1)] === '.'
8,931✔
1692
                && $tokens[($stackPtr + 2)] === '.'
8,931✔
1693
            ) {
2,901✔
1694
                $newToken            = [];
639✔
1695
                $newToken['code']    = T_ELLIPSIS;
639✔
1696
                $newToken['type']    = 'T_ELLIPSIS';
639✔
1697
                $newToken['content'] = '...';
639✔
1698
                $finalTokens[$newStackPtr] = $newToken;
639✔
1699

1700
                $newStackPtr++;
639✔
1701
                $stackPtr += 2;
639✔
1702
                continue;
639✔
1703
            }
1704

1705
            /*
1706
                Before PHP 5.6, the ** operator was tokenized as two
1707
                T_MULTIPLY tokens in a row. So look for and combine
1708
                these tokens in earlier versions.
1709
            */
1710

1711
            if ($tokenIsArray === false
6,030✔
1712
                && $token[0] === '*'
8,931✔
1713
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1714
                && $tokens[($stackPtr + 1)] === '*'
8,931✔
1715
            ) {
2,901✔
1716
                $newToken            = [];
×
1717
                $newToken['code']    = T_POW;
×
1718
                $newToken['type']    = 'T_POW';
×
1719
                $newToken['content'] = '**';
×
1720
                $finalTokens[$newStackPtr] = $newToken;
×
1721

1722
                $newStackPtr++;
×
1723
                $stackPtr++;
×
1724
                continue;
×
1725
            }
1726

1727
            /*
1728
                Before PHP 5.6, the **= operator was tokenized as
1729
                T_MULTIPLY followed by T_MUL_EQUAL. So look for and combine
1730
                these tokens in earlier versions.
1731
            */
1732

1733
            if ($tokenIsArray === false
6,030✔
1734
                && $token[0] === '*'
8,931✔
1735
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1736
                && is_array($tokens[($stackPtr + 1)]) === true
8,931✔
1737
                && $tokens[($stackPtr + 1)][1] === '*='
8,931✔
1738
            ) {
2,901✔
1739
                $newToken            = [];
×
1740
                $newToken['code']    = T_POW_EQUAL;
×
1741
                $newToken['type']    = 'T_POW_EQUAL';
×
1742
                $newToken['content'] = '**=';
×
1743
                $finalTokens[$newStackPtr] = $newToken;
×
1744

1745
                $newStackPtr++;
×
1746
                $stackPtr++;
×
1747
                continue;
×
1748
            }
1749

1750
            /*
1751
                Before PHP 7, the ??= operator was tokenized as
1752
                T_INLINE_THEN, T_INLINE_THEN, T_EQUAL.
1753
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1754
                T_COALESCE, T_EQUAL.
1755
                So look for and combine these tokens in earlier versions.
1756
            */
1757

1758
            if (($tokenIsArray === false
6,030✔
1759
                && $token[0] === '?'
8,931✔
1760
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1761
                && $tokens[($stackPtr + 1)][0] === '?'
8,931✔
1762
                && isset($tokens[($stackPtr + 2)]) === true
8,931✔
1763
                && $tokens[($stackPtr + 2)][0] === '=')
2,901✔
1764
                || ($tokenIsArray === true
6,030✔
1765
                && $token[0] === T_COALESCE
8,931✔
1766
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1767
                && $tokens[($stackPtr + 1)][0] === '=')
8,931✔
1768
            ) {
2,901✔
1769
                $newToken            = [];
×
1770
                $newToken['code']    = T_COALESCE_EQUAL;
×
1771
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1772
                $newToken['content'] = '??=';
×
1773
                $finalTokens[$newStackPtr] = $newToken;
×
1774

1775
                $newStackPtr++;
×
1776
                $stackPtr++;
×
1777

1778
                if ($tokenIsArray === false) {
×
1779
                    // Pre PHP 7.
1780
                    $stackPtr++;
×
1781
                }
1782

1783
                continue;
×
1784
            }
1785

1786
            /*
1787
                Before PHP 7, the ?? operator was tokenized as
1788
                T_INLINE_THEN followed by T_INLINE_THEN.
1789
                So look for and combine these tokens in earlier versions.
1790
            */
1791

1792
            if ($tokenIsArray === false
6,030✔
1793
                && $token[0] === '?'
8,931✔
1794
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1795
                && $tokens[($stackPtr + 1)][0] === '?'
8,931✔
1796
            ) {
2,901✔
1797
                $newToken            = [];
183✔
1798
                $newToken['code']    = T_COALESCE;
183✔
1799
                $newToken['type']    = 'T_COALESCE';
183✔
1800
                $newToken['content'] = '??';
183✔
1801
                $finalTokens[$newStackPtr] = $newToken;
183✔
1802

1803
                $newStackPtr++;
183✔
1804
                $stackPtr++;
183✔
1805
                continue;
183✔
1806
            }
1807

1808
            /*
1809
                Before PHP 8, the ?-> operator was tokenized as
1810
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1811
                So look for and combine these tokens in earlier versions.
1812
            */
1813

1814
            if ($tokenIsArray === false
6,030✔
1815
                && $token[0] === '?'
8,931✔
1816
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1817
                && is_array($tokens[($stackPtr + 1)]) === true
8,931✔
1818
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
8,931✔
1819
            ) {
2,901✔
1820
                $newToken            = [];
162✔
1821
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
162✔
1822
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
162✔
1823
                $newToken['content'] = '?->';
162✔
1824
                $finalTokens[$newStackPtr] = $newToken;
162✔
1825

1826
                $newStackPtr++;
162✔
1827
                $stackPtr++;
162✔
1828
                continue;
162✔
1829
            }
1830

1831
            /*
1832
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1833
                tokens split the token with a T_STRING. So look for
1834
                and change these tokens in earlier versions.
1835
            */
1836

1837
            if (PHP_VERSION_ID < 70400
8,931✔
1838
                && ($tokenIsArray === true
8,931✔
1839
                && ($token[0] === T_LNUMBER
8,931✔
1840
                || $token[0] === T_DNUMBER)
8,931✔
1841
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
1842
                && is_array($tokens[($stackPtr + 1)]) === true
8,931✔
1843
                && $tokens[($stackPtr + 1)][0] === T_STRING
8,931✔
1844
                && $tokens[($stackPtr + 1)][1][0] === '_')
8,931✔
1845
            ) {
2,901✔
1846
                $newContent = $token[1];
162✔
1847
                $newType    = $token[0];
162✔
1848
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
162✔
1849
                    if (is_array($tokens[$i]) === false) {
162✔
1850
                        break;
162✔
1851
                    }
1852

1853
                    if ($tokens[$i][0] === T_LNUMBER
162✔
1854
                        || $tokens[$i][0] === T_DNUMBER
162✔
1855
                    ) {
81✔
1856
                        $newContent .= $tokens[$i][1];
162✔
1857
                        continue;
162✔
1858
                    }
1859

1860
                    if ($tokens[$i][0] === T_STRING
162✔
1861
                        && $tokens[$i][1][0] === '_'
162✔
1862
                        && ((strpos($newContent, '0x') === 0
162✔
1863
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
162✔
1864
                        || (strpos($newContent, '0x') !== 0
162✔
1865
                        && substr($newContent, -1) !== '.'
162✔
1866
                        && substr(strtolower($newContent), -1) !== 'e'
162✔
1867
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
162✔
1868
                    ) {
81✔
1869
                        $newContent .= $tokens[$i][1];
162✔
1870

1871
                        // Support floats.
1872
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
162✔
1873
                            && ($tokens[($i + 1)] === '-'
162✔
1874
                            || $tokens[($i + 1)] === '+')
162✔
1875
                        ) {
81✔
1876
                            $newContent .= $tokens[($i + 1)];
162✔
1877
                            $i++;
162✔
1878
                        }
81✔
1879

1880
                        continue;
162✔
1881
                    }//end if
1882

1883
                    break;
162✔
1884
                }//end for
1885

1886
                if ($newType === T_LNUMBER
81✔
1887
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
162✔
1888
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
162✔
1889
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
162✔
1890
                    || (stripos($newContent, '0x') !== 0
162✔
1891
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
162✔
1892
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
162✔
1893
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
162✔
1894
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
162✔
1895
                ) {
81✔
1896
                    $newType = T_DNUMBER;
162✔
1897
                }
81✔
1898

1899
                $newToken            = [];
162✔
1900
                $newToken['code']    = $newType;
162✔
1901
                $newToken['type']    = Tokens::tokenName($newType);
162✔
1902
                $newToken['content'] = $newContent;
162✔
1903
                $finalTokens[$newStackPtr] = $newToken;
162✔
1904

1905
                $newStackPtr++;
162✔
1906
                $stackPtr = ($i - 1);
162✔
1907
                continue;
162✔
1908
            }//end if
1909

1910
            /*
1911
                Backfill the T_MATCH token for PHP versions < 8.0 and
1912
                do initial correction for non-match expression T_MATCH tokens
1913
                to T_STRING for PHP >= 8.0.
1914
                A final check for non-match expression T_MATCH tokens is done
1915
                in PHP::processAdditional().
1916
            */
1917

1918
            if ($tokenIsArray === true
6,030✔
1919
                && (($token[0] === T_STRING
8,931✔
1920
                && strtolower($token[1]) === 'match')
8,601✔
1921
                || $token[0] === T_MATCH)
8,931✔
1922
            ) {
2,901✔
1923
                $isMatch = false;
2,223✔
1924
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,223✔
1925
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
2,223✔
1926
                        continue;
2,223✔
1927
                    }
1928

1929
                    if ($tokens[$x] !== '(') {
2,223✔
1930
                        // This is not a match expression.
1931
                        break;
1,569✔
1932
                    }
1933

1934
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,223✔
1935
                        // Also not a match expression.
1936
                        break;
549✔
1937
                    }
1938

1939
                    $isMatch = true;
2,223✔
1940
                    break;
2,223✔
1941
                }//end for
1942

1943
                if ($isMatch === true && $token[0] === T_STRING) {
2,223✔
1944
                    $newToken            = [];
1,482✔
1945
                    $newToken['code']    = T_MATCH;
1,482✔
1946
                    $newToken['type']    = 'T_MATCH';
1,482✔
1947
                    $newToken['content'] = $token[1];
1,482✔
1948

1949
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,482✔
1950
                        echo "\t\t* token $stackPtr changed from T_STRING to T_MATCH".PHP_EOL;
×
1951
                    }
1952

1953
                    $finalTokens[$newStackPtr] = $newToken;
1,482✔
1954
                    $newStackPtr++;
1,482✔
1955
                    continue;
1,482✔
1956
                } else if ($isMatch === false && $token[0] === T_MATCH) {
2,127✔
1957
                    // PHP 8.0, match keyword, but not a match expression.
1958
                    $newToken            = [];
183✔
1959
                    $newToken['code']    = T_STRING;
183✔
1960
                    $newToken['type']    = 'T_STRING';
183✔
1961
                    $newToken['content'] = $token[1];
183✔
1962

1963
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
1964
                        echo "\t\t* token $stackPtr changed from T_MATCH to T_STRING".PHP_EOL;
×
1965
                    }
1966

1967
                    $finalTokens[$newStackPtr] = $newToken;
183✔
1968
                    $newStackPtr++;
183✔
1969
                    continue;
183✔
1970
                }//end if
1971
            }//end if
693✔
1972

1973
            /*
1974
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
1975
                to prevent scope being set and the scope for switch default statements
1976
                breaking.
1977
            */
1978

1979
            if ($tokenIsArray === true
6,030✔
1980
                && $token[0] === T_DEFAULT
8,931✔
1981
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
8,931✔
1982
            ) {
2,901✔
1983
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
4,275✔
1984
                    if ($tokens[$x] === ',') {
4,275✔
1985
                        // Skip over potential trailing comma (supported in PHP).
1986
                        continue;
693✔
1987
                    }
1988

1989
                    if (is_array($tokens[$x]) === false
4,275✔
1990
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
4,275✔
1991
                    ) {
1,425✔
1992
                        // Non-empty, non-comma content.
1993
                        break;
4,275✔
1994
                    }
1995
                }
741✔
1996

1997
                if (isset($tokens[$x]) === true
4,275✔
1998
                    && is_array($tokens[$x]) === true
4,275✔
1999
                    && $tokens[$x][0] === T_DOUBLE_ARROW
4,275✔
2000
                ) {
1,425✔
2001
                    // Modify the original token stack for the double arrow so that
2002
                    // future checks can disregard the double arrow token more easily.
2003
                    // For match expression "case" statements, this is handled
2004
                    // in PHP::processAdditional().
2005
                    $tokens[$x][0] = T_MATCH_ARROW;
2,223✔
2006
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,223✔
2007
                        echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
2008
                    }
2009

2010
                    $newToken            = [];
2,223✔
2011
                    $newToken['code']    = T_MATCH_DEFAULT;
2,223✔
2012
                    $newToken['type']    = 'T_MATCH_DEFAULT';
2,223✔
2013
                    $newToken['content'] = $token[1];
2,223✔
2014

2015
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,223✔
2016
                        echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
×
2017
                    }
2018

2019
                    $finalTokens[$newStackPtr] = $newToken;
2,223✔
2020
                    $newStackPtr++;
2,223✔
2021
                    continue;
2,223✔
2022
                }//end if
2023
            }//end if
1,425✔
2024

2025
            /*
2026
                Convert ? to T_NULLABLE OR T_INLINE_THEN
2027
            */
2028

2029
            if ($tokenIsArray === false && $token[0] === '?') {
8,931✔
2030
                $newToken            = [];
3,540✔
2031
                $newToken['content'] = '?';
3,540✔
2032

2033
                // For typed constants, we only need to check the token before the ? to be sure.
2034
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
3,540✔
2035
                    $newToken['code'] = T_NULLABLE;
453✔
2036
                    $newToken['type'] = 'T_NULLABLE';
453✔
2037

2038
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
453✔
2039
                        echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2040
                    }
2041

2042
                    $finalTokens[$newStackPtr] = $newToken;
453✔
2043
                    $newStackPtr++;
453✔
2044
                    continue;
453✔
2045
                }
2046

2047
                /*
2048
                 * Check if the next non-empty token is one of the tokens which can be used
2049
                 * in type declarations. If not, it's definitely a ternary.
2050
                 * At this point, the only token types which need to be taken into consideration
2051
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
2052
                 */
2053

2054
                $lastRelevantNonEmpty = null;
3,540✔
2055

2056
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
3,540✔
2057
                    if (is_array($tokens[$i]) === true) {
3,540✔
2058
                        $tokenType = $tokens[$i][0];
3,540✔
2059
                    } else {
1,104✔
2060
                        $tokenType = $tokens[$i];
2,841✔
2061
                    }
2062

2063
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
3,540✔
2064
                        continue;
3,540✔
2065
                    }
2066

2067
                    if ($tokenType === T_STRING
2,436✔
2068
                        || $tokenType === T_NAME_FULLY_QUALIFIED
3,540✔
2069
                        || $tokenType === T_NAME_RELATIVE
3,540✔
2070
                        || $tokenType === T_NAME_QUALIFIED
3,540✔
2071
                        || $tokenType === T_ARRAY
3,540✔
2072
                        || $tokenType === T_NAMESPACE
3,540✔
2073
                        || $tokenType === T_NS_SEPARATOR
3,540✔
2074
                    ) {
1,104✔
2075
                        $lastRelevantNonEmpty = $tokenType;
2,841✔
2076
                        continue;
2,841✔
2077
                    }
2078

2079
                    if (($tokenType !== T_CALLABLE
2,436✔
2080
                        && isset($lastRelevantNonEmpty) === false)
3,540✔
2081
                        || ($lastRelevantNonEmpty === T_ARRAY
1,833✔
2082
                        && $tokenType === '(')
2,841✔
2083
                        || (($lastRelevantNonEmpty === T_STRING
3,234✔
2084
                        || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED
2,226✔
2085
                        || $lastRelevantNonEmpty === T_NAME_RELATIVE
1,218✔
2086
                        || $lastRelevantNonEmpty === T_NAME_QUALIFIED)
2,226✔
2087
                        && ($tokenType === T_DOUBLE_COLON
3,234✔
2088
                        || $tokenType === '('
3,234✔
2089
                        || $tokenType === ':'))
3,444✔
2090
                    ) {
1,104✔
2091
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,117✔
2092
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2093
                        }
2094

2095
                        $newToken['code'] = T_INLINE_THEN;
3,117✔
2096
                        $newToken['type'] = 'T_INLINE_THEN';
3,117✔
2097

2098
                        $insideInlineIf[] = $stackPtr;
3,117✔
2099

2100
                        $finalTokens[$newStackPtr] = $newToken;
3,117✔
2101
                        $newStackPtr++;
3,117✔
2102
                        continue 2;
3,117✔
2103
                    }
2104

2105
                    break;
423✔
2106
                }//end for
2107

2108
                /*
2109
                 * This can still be a nullable type or a ternary.
2110
                 * Do additional checking.
2111
                 */
2112

2113
                $prevNonEmpty     = null;
486✔
2114
                $lastSeenNonEmpty = null;
486✔
2115

2116
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
486✔
2117
                    if (is_array($tokens[$i]) === true) {
486✔
2118
                        $tokenType = $tokens[$i][0];
486✔
2119
                    } else {
162✔
2120
                        $tokenType = $tokens[$i];
486✔
2121
                    }
2122

2123
                    if ($tokenType === T_STATIC
324✔
2124
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
324✔
2125
                        || $lastSeenNonEmpty === '(')
324✔
2126
                    ) {
162✔
2127
                        $lastSeenNonEmpty = $tokenType;
×
2128
                        continue;
×
2129
                    }
2130

2131
                    if ($prevNonEmpty === null
324✔
2132
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
486✔
2133
                    ) {
162✔
2134
                        // Found the previous non-empty token.
2135
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
486✔
2136
                            $newToken['code'] = T_NULLABLE;
405✔
2137
                            $newToken['type'] = 'T_NULLABLE';
405✔
2138

2139
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
405✔
2140
                                echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2141
                            }
2142

2143
                            break;
405✔
2144
                        }
2145

2146
                        $prevNonEmpty = $tokenType;
486✔
2147
                    }
162✔
2148

2149
                    if ($tokenType === T_FUNCTION
324✔
2150
                        || $tokenType === T_FN
486✔
2151
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
486✔
2152
                        || $tokenType === T_VAR
486✔
2153
                        || $tokenType === T_READONLY
486✔
2154
                    ) {
162✔
2155
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
423✔
2156
                            echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
2157
                        }
2158

2159
                        $newToken['code'] = T_NULLABLE;
423✔
2160
                        $newToken['type'] = 'T_NULLABLE';
423✔
2161
                        break;
423✔
2162
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
486✔
2163
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
81✔
2164
                            echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
×
2165
                        }
2166

2167
                        $newToken['code'] = T_INLINE_THEN;
81✔
2168
                        $newToken['type'] = 'T_INLINE_THEN';
81✔
2169

2170
                        $insideInlineIf[] = $stackPtr;
81✔
2171
                        break;
81✔
2172
                    }
2173

2174
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
486✔
2175
                        $lastSeenNonEmpty = $tokenType;
486✔
2176
                    }
162✔
2177
                }//end for
162✔
2178

2179
                $finalTokens[$newStackPtr] = $newToken;
486✔
2180
                $newStackPtr++;
486✔
2181
                continue;
486✔
2182
            }//end if
2183

2184
            /*
2185
                Tokens after a double colon may look like scope openers,
2186
                such as when writing code like Foo::NAMESPACE, but they are
2187
                only ever variables or strings.
2188
            */
2189

2190
            if ($stackPtr > 1
6,030✔
2191
                && (is_array($tokens[($stackPtr - 1)]) === true
8,931✔
2192
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
8,931✔
2193
                && $tokenIsArray === true
8,931✔
2194
                && $token[0] !== T_STRING
8,931✔
2195
                && $token[0] !== T_VARIABLE
8,931✔
2196
                && $token[0] !== T_DOLLAR
8,931✔
2197
                && isset(Tokens::$emptyTokens[$token[0]]) === false
8,931✔
2198
            ) {
2,901✔
2199
                $newToken            = [];
×
2200
                $newToken['code']    = T_STRING;
×
2201
                $newToken['type']    = 'T_STRING';
×
2202
                $newToken['content'] = $token[1];
×
2203
                $finalTokens[$newStackPtr] = $newToken;
×
2204

2205
                $newStackPtr++;
×
2206
                continue;
×
2207
            }
2208

2209
            /*
2210
                Backfill the T_FN token for PHP versions < 7.4.
2211
            */
2212

2213
            if ($tokenIsArray === true
6,030✔
2214
                && $token[0] === T_STRING
8,931✔
2215
                && strtolower($token[1]) === 'fn'
8,931✔
2216
            ) {
2,901✔
2217
                // Modify the original token stack so that
2218
                // future checks (like looking for T_NULLABLE) can
2219
                // detect the T_FN token more easily.
2220
                $tokens[$stackPtr][0] = T_FN;
3,840✔
2221
                $token[0] = T_FN;
3,840✔
2222
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,840✔
2223
                    echo "\t\t* token $stackPtr changed from T_STRING to T_FN".PHP_EOL;
×
2224
                }
2225
            }
1,920✔
2226

2227
            /*
2228
                This is a special condition for T_ARRAY tokens used for
2229
                function return types. We want to keep the parenthesis map clean,
2230
                so let's tag these tokens as T_STRING.
2231
            */
2232

2233
            if ($tokenIsArray === true
6,030✔
2234
                && ($token[0] === T_FUNCTION
8,931✔
2235
                || $token[0] === T_FN)
8,931✔
2236
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
8,931✔
2237
            ) {
2,901✔
2238
                // Go looking for the colon to start the return type hint.
2239
                // Start by finding the closing parenthesis of the function.
2240
                $parenthesisStack  = [];
7,065✔
2241
                $parenthesisCloser = false;
7,065✔
2242
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
7,065✔
2243
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
7,065✔
2244
                        $parenthesisStack[] = $x;
7,065✔
2245
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
7,065✔
2246
                        array_pop($parenthesisStack);
7,065✔
2247
                        if (empty($parenthesisStack) === true) {
7,065✔
2248
                            $parenthesisCloser = $x;
7,065✔
2249
                            break;
7,065✔
2250
                        }
2251
                    }
588✔
2252
                }
2,355✔
2253

2254
                if ($parenthesisCloser !== false) {
7,065✔
2255
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
7,065✔
2256
                        if (is_array($tokens[$x]) === false
7,065✔
2257
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
7,065✔
2258
                        ) {
2,355✔
2259
                            // Non-empty content.
2260
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
7,065✔
2261
                                // Found a use statements, so search ahead for the closing parenthesis.
2262
                                for ($x += 1; $x < $numTokens; $x++) {
171✔
2263
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
171✔
2264
                                        continue(2);
171✔
2265
                                    }
2266
                                }
57✔
2267
                            }
2268

2269
                            break;
7,065✔
2270
                        }
2271
                    }
2,292✔
2272

2273
                    if (isset($tokens[$x]) === true
7,065✔
2274
                        && is_array($tokens[$x]) === false
7,065✔
2275
                        && $tokens[$x] === ':'
7,065✔
2276
                    ) {
2,355✔
2277
                        // Find the start of the return type.
2278
                        for ($x += 1; $x < $numTokens; $x++) {
5,850✔
2279
                            if (is_array($tokens[$x]) === true
5,850✔
2280
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
5,850✔
2281
                            ) {
1,950✔
2282
                                // Whitespace or comments before the return type.
2283
                                continue;
5,850✔
2284
                            }
2285

2286
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
5,850✔
2287
                                // Found a nullable operator, so skip it.
2288
                                // But also convert the token to save the tokenizer
2289
                                // a bit of time later on.
2290
                                $tokens[$x] = [
405✔
2291
                                    T_NULLABLE,
405✔
2292
                                    '?',
405✔
2293
                                ];
135✔
2294

2295
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
405✔
2296
                                    echo "\t\t* token $x changed from ? to T_NULLABLE".PHP_EOL;
×
2297
                                }
2298

2299
                                continue;
405✔
2300
                            }
2301

2302
                            break;
5,850✔
2303
                        }//end for
2304
                    }//end if
1,950✔
2305
                }//end if
2,355✔
2306
            }//end if
2,355✔
2307

2308
            /*
2309
                Before PHP 7, the <=> operator was tokenized as
2310
                T_IS_SMALLER_OR_EQUAL followed by T_GREATER_THAN.
2311
                So look for and combine these tokens in earlier versions.
2312
            */
2313

2314
            if ($tokenIsArray === true
6,030✔
2315
                && $token[0] === T_IS_SMALLER_OR_EQUAL
8,931✔
2316
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
2317
                && $tokens[($stackPtr + 1)][0] === '>'
8,931✔
2318
            ) {
2,901✔
2319
                $newToken            = [];
×
2320
                $newToken['code']    = T_SPACESHIP;
×
2321
                $newToken['type']    = 'T_SPACESHIP';
×
2322
                $newToken['content'] = '<=>';
×
2323
                $finalTokens[$newStackPtr] = $newToken;
×
2324

2325
                $newStackPtr++;
×
2326
                $stackPtr++;
×
2327
                continue;
×
2328
            }
2329

2330
            /*
2331
                PHP doesn't assign a token to goto labels, so we have to.
2332
                These are just string tokens with a single colon after them. Double
2333
                colons are already tokenized and so don't interfere with this check.
2334
                But we do have to account for CASE statements, that look just like
2335
                goto labels.
2336
            */
2337

2338
            if ($tokenIsArray === true
6,030✔
2339
                && $token[0] === T_STRING
8,931✔
2340
                && isset($tokens[($stackPtr + 1)]) === true
8,931✔
2341
                && $tokens[($stackPtr + 1)] === ':'
8,931✔
2342
                && (is_array($tokens[($stackPtr - 1)]) === false
7,287✔
2343
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
7,401✔
2344
            ) {
2,901✔
2345
                $stopTokens = [
1,371✔
2346
                    T_CASE               => true,
4,113✔
2347
                    T_SEMICOLON          => true,
4,113✔
2348
                    T_OPEN_TAG           => true,
4,113✔
2349
                    T_OPEN_CURLY_BRACKET => true,
4,113✔
2350
                    T_INLINE_THEN        => true,
4,113✔
2351
                    T_ENUM               => true,
4,113✔
2352
                ];
2,742✔
2353

2354
                for ($x = ($newStackPtr - 1); $x > 0; $x--) {
4,113✔
2355
                    if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
4,113✔
2356
                        break;
4,113✔
2357
                    }
2358
                }
1,371✔
2359

2360
                if ($finalTokens[$x]['code'] !== T_CASE
4,113✔
2361
                    && $finalTokens[$x]['code'] !== T_INLINE_THEN
4,113✔
2362
                    && $finalTokens[$x]['code'] !== T_ENUM
4,113✔
2363
                ) {
1,371✔
2364
                    $finalTokens[$newStackPtr] = [
324✔
2365
                        'content' => $token[1].':',
324✔
2366
                        'code'    => T_GOTO_LABEL,
324✔
2367
                        'type'    => 'T_GOTO_LABEL',
324✔
2368
                    ];
108✔
2369

2370
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
324✔
2371
                        echo "\t\t* token $stackPtr changed from T_STRING to T_GOTO_LABEL".PHP_EOL;
×
2372
                        echo "\t\t* skipping T_COLON token ".($stackPtr + 1).PHP_EOL;
×
2373
                    }
2374

2375
                    $newStackPtr++;
324✔
2376
                    $stackPtr++;
324✔
2377
                    continue;
324✔
2378
                }
2379
            }//end if
1,371✔
2380

2381
            /*
2382
                If this token has newlines in its content, split each line up
2383
                and create a new token for each line. We do this so it's easier
2384
                to ascertain where errors occur on a line.
2385
                Note that $token[1] is the token's content.
2386
            */
2387

2388
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
8,931✔
2389
                $tokenLines = explode($this->eolChar, $token[1]);
8,931✔
2390
                $numLines   = count($tokenLines);
8,931✔
2391
                $newToken   = [
3,015✔
2392
                    'type'    => Tokens::tokenName($token[0]),
8,931✔
2393
                    'code'    => $token[0],
8,931✔
2394
                    'content' => '',
8,931✔
2395
                ];
5,916✔
2396

2397
                for ($i = 0; $i < $numLines; $i++) {
8,931✔
2398
                    $newToken['content'] = $tokenLines[$i];
8,931✔
2399
                    if ($i === ($numLines - 1)) {
8,931✔
2400
                        if ($tokenLines[$i] === '') {
8,931✔
2401
                            break;
8,931✔
2402
                        }
2403
                    } else {
2,676✔
2404
                        $newToken['content'] .= $this->eolChar;
8,931✔
2405
                    }
2406

2407
                    $finalTokens[$newStackPtr] = $newToken;
8,931✔
2408
                    $newStackPtr++;
8,931✔
2409
                }
2,901✔
2410
            } else {
2,901✔
2411
                // Some T_STRING tokens should remain that way due to their context.
2412
                if ($tokenIsArray === true && $token[0] === T_STRING) {
8,931✔
2413
                    $preserveTstring = false;
7,941✔
2414

2415
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2416
                    // but the constant name should not be.
2417
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
7,941✔
2418
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
7,713✔
2419
                        || $insideConstDeclaration === true
5,370✔
2420
                    ) {
2,571✔
2421
                        // Find the next non-empty token.
2422
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
4,950✔
2423
                            if (is_array($tokens[$i]) === true
4,950✔
2424
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
4,950✔
2425
                            ) {
1,647✔
2426
                                continue;
4,545✔
2427
                            }
2428

2429
                            break;
4,950✔
2430
                        }
2431

2432
                        if ($tokens[$i] === '=') {
4,950✔
2433
                            $preserveTstring        = true;
4,365✔
2434
                            $insideConstDeclaration = false;
4,560✔
2435
                        }
1,452✔
2436
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
7,941✔
2437
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
7,941✔
2438
                    ) {
2,571✔
2439
                        $preserveTstring = true;
7,599✔
2440

2441
                        // Special case for syntax like: return new self/new parent
2442
                        // where self/parent should not be a string.
2443
                        $tokenContentLower = strtolower($token[1]);
7,599✔
2444
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
7,599✔
2445
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
7,599✔
2446
                        ) {
2,457✔
2447
                            $preserveTstring = false;
4,809✔
2448
                        }
1,119✔
2449
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
7,899✔
2450
                        // Function names for functions declared to return by reference.
2451
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
3,141✔
2452
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
3,141✔
2453
                                continue;
1,440✔
2454
                            }
2455

2456
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
3,141✔
2457
                                $preserveTstring = true;
1,440✔
2458
                            }
480✔
2459

2460
                            break;
3,141✔
2461
                        }
2462
                    } else {
1,047✔
2463
                        // Keywords with special PHPCS token when used as a function call.
2464
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
7,878✔
2465
                            if (is_array($tokens[$i]) === true
7,878✔
2466
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
7,878✔
2467
                            ) {
2,550✔
2468
                                continue;
6,759✔
2469
                            }
2470

2471
                            if ($tokens[$i][0] === '(') {
7,878✔
2472
                                $preserveTstring = true;
6,660✔
2473
                            }
2,220✔
2474

2475
                            break;
7,878✔
2476
                        }
2477
                    }//end if
2478

2479
                    if ($preserveTstring === true) {
7,941✔
2480
                        $finalTokens[$newStackPtr] = [
7,608✔
2481
                            'code'    => T_STRING,
7,608✔
2482
                            'type'    => 'T_STRING',
7,608✔
2483
                            'content' => $token[1],
7,608✔
2484
                        ];
2,574✔
2485

2486
                        $newStackPtr++;
7,608✔
2487
                        continue;
7,608✔
2488
                    }
2489
                }//end if
2,547✔
2490

2491
                $newToken = null;
8,931✔
2492
                if ($tokenIsArray === false) {
8,931✔
2493
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
8,922✔
2494
                        $newToken = self::$resolveTokenCache[$token[0]];
8,922✔
2495
                    }
2,898✔
2496
                } else {
2,898✔
2497
                    $cacheKey = null;
8,931✔
2498
                    if ($token[0] === T_STRING) {
8,931✔
2499
                        $cacheKey = strtolower($token[1]);
7,869✔
2500
                    } else if ($token[0] !== T_CURLY_OPEN) {
8,931✔
2501
                        $cacheKey = $token[0];
8,931✔
2502
                    }
2,901✔
2503

2504
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
8,931✔
2505
                        $newToken            = self::$resolveTokenCache[$cacheKey];
8,931✔
2506
                        $newToken['content'] = $token[1];
8,931✔
2507
                    }
2,901✔
2508
                }
2509

2510
                if ($newToken === null) {
8,931✔
2511
                    $newToken = self::standardiseToken($token);
150✔
2512
                }
51✔
2513

2514
                // Convert colons that are actually the ELSE component of an
2515
                // inline IF statement.
2516
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
8,931✔
2517
                    $isInlineIf = true;
3,117✔
2518

2519
                    // Make sure this isn't a named parameter label.
2520
                    // Get the previous non-empty token.
2521
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
3,117✔
2522
                        if (is_array($tokens[$i]) === false
3,117✔
2523
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
3,117✔
2524
                        ) {
963✔
2525
                            break;
3,117✔
2526
                        }
2527
                    }
963✔
2528

2529
                    if ($tokens[$i][0] === T_PARAM_NAME) {
3,117✔
2530
                        $isInlineIf = false;
1,917✔
2531
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,917✔
2532
                            echo "\t\t* token is parameter label, not T_INLINE_ELSE".PHP_EOL;
×
2533
                        }
2534
                    }
639✔
2535

2536
                    if ($isInlineIf === true) {
3,117✔
2537
                        // Make sure this isn't a return type separator.
2538
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
3,117✔
2539
                            if (is_array($tokens[$i]) === false
3,117✔
2540
                                || ($tokens[$i][0] !== T_DOC_COMMENT
3,117✔
2541
                                && $tokens[$i][0] !== T_COMMENT
3,117✔
2542
                                && $tokens[$i][0] !== T_WHITESPACE)
3,117✔
2543
                            ) {
963✔
2544
                                break;
3,117✔
2545
                            }
2546
                        }
963✔
2547

2548
                        if ($tokens[$i] === ')') {
3,117✔
2549
                            $parenCount = 1;
1,917✔
2550
                            for ($i--; $i > 0; $i--) {
1,917✔
2551
                                if ($tokens[$i] === '(') {
1,917✔
2552
                                    $parenCount--;
1,917✔
2553
                                    if ($parenCount === 0) {
1,917✔
2554
                                        break;
1,917✔
2555
                                    }
2556
                                } else if ($tokens[$i] === ')') {
1,917✔
2557
                                    $parenCount++;
×
2558
                                }
2559
                            }
639✔
2560

2561
                            // We've found the open parenthesis, so if the previous
2562
                            // non-empty token is FUNCTION or USE, this is a return type.
2563
                            // Note that we need to skip T_STRING tokens here as these
2564
                            // can be function names.
2565
                            for ($i--; $i > 0; $i--) {
1,917✔
2566
                                if (is_array($tokens[$i]) === false
1,917✔
2567
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
1,917✔
2568
                                    && $tokens[$i][0] !== T_COMMENT
1,917✔
2569
                                    && $tokens[$i][0] !== T_WHITESPACE
1,917✔
2570
                                    && $tokens[$i][0] !== T_STRING)
1,917✔
2571
                                ) {
639✔
2572
                                    break;
1,917✔
2573
                                }
2574
                            }
639✔
2575

2576
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
1,917✔
2577
                                $isInlineIf = false;
1,917✔
2578
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,917✔
2579
                                    echo "\t\t* token is return type, not T_INLINE_ELSE".PHP_EOL;
×
2580
                                }
2581
                            }
639✔
2582
                        }//end if
639✔
2583
                    }//end if
963✔
2584

2585
                    // Check to see if this is a CASE or DEFAULT opener.
2586
                    if ($isInlineIf === true) {
3,117✔
2587
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
3,117✔
2588
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
3,117✔
2589
                            if (is_array($tokens[$i]) === true
3,117✔
2590
                                && ($tokens[$i][0] === T_CASE
3,117✔
2591
                                || $tokens[$i][0] === T_DEFAULT)
3,117✔
2592
                            ) {
963✔
2593
                                $isInlineIf = false;
×
2594
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2595
                                    echo "\t\t* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE".PHP_EOL;
×
2596
                                }
2597

2598
                                break;
×
2599
                            }
2600

2601
                            if (is_array($tokens[$i]) === false
3,117✔
2602
                                && ($tokens[$i] === ';'
3,117✔
2603
                                || $tokens[$i] === '{'
3,117✔
2604
                                || $tokens[$i] === '}')
3,117✔
2605
                            ) {
963✔
2606
                                break;
2,466✔
2607
                            }
2608
                        }//end for
963✔
2609
                    }//end if
963✔
2610

2611
                    if ($isInlineIf === true) {
3,117✔
2612
                        array_pop($insideInlineIf);
3,117✔
2613
                        $newToken['code'] = T_INLINE_ELSE;
3,117✔
2614
                        $newToken['type'] = 'T_INLINE_ELSE';
3,117✔
2615

2616
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,117✔
2617
                            echo "\t\t* token changed from T_COLON to T_INLINE_ELSE".PHP_EOL;
×
2618
                        }
2619
                    }
963✔
2620
                }//end if
963✔
2621

2622
                // This is a special condition for T_ARRAY tokens used for anything else
2623
                // but array declarations, like type hinting function arguments as
2624
                // being arrays.
2625
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2626
                // T_STRING.
2627
                if ($newToken['code'] === T_ARRAY) {
8,931✔
2628
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
4,440✔
2629
                        if (is_array($tokens[$i]) === false
4,440✔
2630
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
4,440✔
2631
                        ) {
1,404✔
2632
                            // Non-empty content.
2633
                            break;
4,440✔
2634
                        }
2635
                    }
1,173✔
2636

2637
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
4,440✔
2638
                        $newToken['code'] = T_STRING;
3,747✔
2639
                        $newToken['type'] = 'T_STRING';
3,747✔
2640
                    }
1,173✔
2641
                }
1,404✔
2642

2643
                // This is a special case when checking PHP 5.5+ code in PHP < 5.5
2644
                // where "finally" should be T_FINALLY instead of T_STRING.
2645
                if ($newToken['code'] === T_STRING
8,931✔
2646
                    && strtolower($newToken['content']) === 'finally'
8,931✔
2647
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CLOSE_CURLY_BRACKET
8,931✔
2648
                ) {
2,901✔
2649
                    $newToken['code'] = T_FINALLY;
528✔
2650
                    $newToken['type'] = 'T_FINALLY';
528✔
2651
                }
528✔
2652

2653
                // This is a special case for PHP 5.6 use function and use const
2654
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2655
                // and T_CONST.
2656
                if (($newToken['code'] === T_FUNCTION
8,931✔
2657
                    || $newToken['code'] === T_CONST)
8,931✔
2658
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
8,931✔
2659
                ) {
2,901✔
2660
                    $newToken['code'] = T_STRING;
405✔
2661
                    $newToken['type'] = 'T_STRING';
405✔
2662
                }
135✔
2663

2664
                // This is a special case for use groups in PHP 7+ where leaving
2665
                // the curly braces as their normal tokens would confuse
2666
                // the scope map and sniffs.
2667
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
8,931✔
2668
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
8,931✔
2669
                ) {
2,901✔
2670
                    $newToken['code'] = T_OPEN_USE_GROUP;
405✔
2671
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
405✔
2672
                    $insideUseGroup   = true;
405✔
2673
                }
135✔
2674

2675
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
8,931✔
2676
                    $newToken['code'] = T_CLOSE_USE_GROUP;
405✔
2677
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
405✔
2678
                    $insideUseGroup   = false;
405✔
2679
                }
135✔
2680

2681
                $finalTokens[$newStackPtr] = $newToken;
8,931✔
2682
                $newStackPtr++;
8,931✔
2683
            }//end if
2684
        }//end for
2,901✔
2685

2686
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
8,931✔
2687
            echo "\t*** END PHP TOKENIZING ***".PHP_EOL;
×
2688
        }
2689

2690
        return $finalTokens;
8,931✔
2691

2692
    }//end tokenize()
2693

2694

2695
    /**
2696
     * Performs additional processing after main tokenizing.
2697
     *
2698
     * This additional processing checks for CASE statements that are using curly
2699
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2700
     * into T_CLOSURE when they are not standard function definitions. It also
2701
     * detects short array syntax and converts those square brackets into new tokens.
2702
     * It also corrects some usage of the static and class keywords. It also
2703
     * assigns tokens to function return types.
2704
     *
2705
     * @return void
2706
     */
2707
    protected function processAdditional()
5,007✔
2708
    {
2709
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
5,007✔
2710
            echo "\t*** START ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
2711
        }
2712

2713
        $this->createAttributesNestingMap();
5,007✔
2714

2715
        $numTokens         = count($this->tokens);
5,007✔
2716
        $lastSeenTypeToken = $numTokens;
5,007✔
2717

2718
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
5,007✔
2719
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2720
            if (isset($this->tokens[$i]['scope_opener']) === true
5,007✔
2721
                && isset($this->tokens[$i]['scope_condition']) === false
5,007✔
2722
            ) {
1,593✔
2723
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2724
            }
2725

2726
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
5,007✔
2727
                /*
2728
                    Detect functions that are actually closures and
2729
                    assign them a different token.
2730
                */
2731

2732
                if (isset($this->tokens[$i]['scope_opener']) === true) {
4,284✔
2733
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
4,248✔
2734
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
4,248✔
2735
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
4,248✔
2736
                        ) {
1,416✔
2737
                            break;
4,248✔
2738
                        }
2739
                    }
1,416✔
2740

2741
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
4,248✔
2742
                        $this->tokens[$i]['code'] = T_CLOSURE;
3,159✔
2743
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
3,159✔
2744
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,159✔
2745
                            $line = $this->tokens[$i]['line'];
×
2746
                            echo "\t* token $i on line $line changed from T_FUNCTION to T_CLOSURE".PHP_EOL;
×
2747
                        }
2748

2749
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
3,159✔
2750
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,926✔
2751
                                continue;
×
2752
                            }
2753

2754
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
1,926✔
2755
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,926✔
2756
                                $type = $this->tokens[$x]['type'];
×
2757
                                echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2758
                            }
2759
                        }
642✔
2760
                    }
1,053✔
2761
                }//end if
1,416✔
2762

2763
                continue;
4,284✔
2764
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
5,007✔
2765
                /*
2766
                    Detect anonymous classes and assign them a different token.
2767
                */
2768

2769
                for ($x = ($i + 1); $x < $numTokens; $x++) {
4,296✔
2770
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
4,296✔
2771
                        break;
4,296✔
2772
                    }
2773
                }
1,356✔
2774

2775
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
4,296✔
2776
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
4,296✔
2777
                    || $this->tokens[$x]['code'] === T_EXTENDS
4,242✔
2778
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
4,269✔
2779
                ) {
1,356✔
2780
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
3,090✔
2781
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
3,090✔
2782
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,090✔
2783
                        $line = $this->tokens[$i]['line'];
×
2784
                        echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL;
×
2785
                    }
2786

2787
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
3,090✔
2788
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
3,090✔
2789
                    ) {
954✔
2790
                        $closer = $this->tokens[$x]['parenthesis_closer'];
1,857✔
2791

2792
                        $this->tokens[$i]['parenthesis_opener']     = $x;
1,857✔
2793
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
1,857✔
2794
                        $this->tokens[$i]['parenthesis_owner']      = $i;
1,857✔
2795
                        $this->tokens[$x]['parenthesis_owner']      = $i;
1,857✔
2796
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
1,857✔
2797

2798
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,857✔
2799
                            $line = $this->tokens[$i]['line'];
×
2800
                            echo "\t\t* added parenthesis keys to T_ANON_CLASS token $i on line $line".PHP_EOL;
×
2801
                        }
2802
                    }
543✔
2803

2804
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
3,090✔
2805
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
3,090✔
2806
                            continue;
×
2807
                        }
2808

2809
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
3,090✔
2810
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,090✔
2811
                            $type = $this->tokens[$x]['type'];
×
2812
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
2813
                        }
2814
                    }
954✔
2815
                }//end if
954✔
2816

2817
                continue;
4,296✔
2818
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
5,007✔
2819
                // Possible arrow function.
2820
                for ($x = ($i + 1); $x < $numTokens; $x++) {
3,411✔
2821
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
3,411✔
2822
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
3,411✔
2823
                    ) {
1,137✔
2824
                        // Non-whitespace content.
2825
                        break;
3,411✔
2826
                    }
2827
                }
1,035✔
2828

2829
                if (isset($this->tokens[$x]) === true
3,411✔
2830
                    && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
3,411✔
2831
                    && isset($this->tokens[$x]['parenthesis_closer']) === true
3,411✔
2832
                ) {
1,137✔
2833
                    $ignore  = Tokens::$emptyTokens;
3,402✔
2834
                    $ignore += [
1,134✔
2835
                        T_ARRAY                  => T_ARRAY,
3,402✔
2836
                        T_CALLABLE               => T_CALLABLE,
3,402✔
2837
                        T_COLON                  => T_COLON,
3,402✔
2838
                        T_NAMESPACE              => T_NAMESPACE,
3,402✔
2839
                        T_NS_SEPARATOR           => T_NS_SEPARATOR,
3,402✔
2840
                        T_NULL                   => T_NULL,
3,402✔
2841
                        T_TRUE                   => T_TRUE,
3,402✔
2842
                        T_FALSE                  => T_FALSE,
3,402✔
2843
                        T_NULLABLE               => T_NULLABLE,
3,402✔
2844
                        T_PARENT                 => T_PARENT,
3,402✔
2845
                        T_SELF                   => T_SELF,
3,402✔
2846
                        T_STATIC                 => T_STATIC,
3,402✔
2847
                        T_STRING                 => T_STRING,
3,402✔
2848
                        T_TYPE_UNION             => T_TYPE_UNION,
3,402✔
2849
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
3,402✔
2850
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
3,402✔
2851
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
3,402✔
2852
                    ];
1,134✔
2853

2854
                    $closer = $this->tokens[$x]['parenthesis_closer'];
3,402✔
2855
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
3,402✔
2856
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
3,402✔
2857
                            break;
3,402✔
2858
                        }
2859
                    }
1,134✔
2860

2861
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
3,402✔
2862
                        $endTokens = [
1,134✔
2863
                            T_COLON                => true,
3,402✔
2864
                            T_COMMA                => true,
3,402✔
2865
                            T_SEMICOLON            => true,
3,402✔
2866
                            T_CLOSE_PARENTHESIS    => true,
3,402✔
2867
                            T_CLOSE_SQUARE_BRACKET => true,
3,402✔
2868
                            T_CLOSE_CURLY_BRACKET  => true,
3,402✔
2869
                            T_CLOSE_SHORT_ARRAY    => true,
3,402✔
2870
                            T_OPEN_TAG             => true,
3,402✔
2871
                            T_CLOSE_TAG            => true,
3,402✔
2872
                        ];
2,268✔
2873

2874
                        $inTernary    = false;
3,402✔
2875
                        $lastEndToken = null;
3,402✔
2876

2877
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
3,402✔
2878
                            // Arrow function closer should never be shared with the closer of a match
2879
                            // control structure.
2880
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
3,402✔
2881
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
3,402✔
2882
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
3,402✔
2883
                            ) {
1,134✔
2884
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
1,269✔
2885
                                    // Match in return value of arrow function. Move on to the next token.
2886
                                    continue;
1,269✔
2887
                                }
2888

2889
                                // Arrow function as return value for the last match case without trailing comma.
2890
                                if ($lastEndToken !== null) {
1,269✔
2891
                                    $scopeCloser = $lastEndToken;
1,269✔
2892
                                    break;
1,269✔
2893
                                }
2894

2895
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
558✔
2896
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
558✔
2897
                                        $scopeCloser = $lastNonEmpty;
558✔
2898
                                        break 2;
558✔
2899
                                    }
2900
                                }
186✔
2901
                            }
2902

2903
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
3,402✔
2904
                                if ($lastEndToken !== null
2,268✔
2905
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
2,991✔
2906
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
2,043✔
2907
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
2,580✔
2908
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
2,991✔
2909
                                ) {
1,134✔
2910
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
558✔
2911
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
558✔
2912
                                            $scopeCloser = $lastNonEmpty;
558✔
2913
                                            break;
558✔
2914
                                        }
2915
                                    }
186✔
2916
                                }
186✔
2917

2918
                                break;
3,402✔
2919
                            }
2920

2921
                            if ($inTernary === false
2,268✔
2922
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
3,402✔
2923
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
3,402✔
2924
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
3,402✔
2925
                            ) {
1,134✔
2926
                                // Found a nested arrow function that already has the closer set and is in
2927
                                // the same scope as us, so we can use its closer.
2928
                                break;
558✔
2929
                            }
2930

2931
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
3,402✔
2932
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
3,402✔
2933
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
3,402✔
2934
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
3,402✔
2935
                            ) {
1,134✔
2936
                                // We minus 1 here in case the closer can be shared with us.
2937
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
1,269✔
2938
                                continue;
1,269✔
2939
                            }
2940

2941
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
3,402✔
2942
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
2,169✔
2943
                                $lastEndToken = $scopeCloser;
2,169✔
2944
                                continue;
2,169✔
2945
                            }
2946

2947
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
3,402✔
2948
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
558✔
2949
                                $lastEndToken = $scopeCloser;
558✔
2950
                                continue;
558✔
2951
                            }
2952

2953
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
3,402✔
2954
                                $inTernary = true;
558✔
2955
                                continue;
558✔
2956
                            }
2957

2958
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
3,402✔
2959
                                if ($inTernary === false) {
558✔
2960
                                    break;
558✔
2961
                                }
2962

2963
                                $inTernary = false;
558✔
2964
                                continue;
558✔
2965
                            }
2966
                        }//end for
1,134✔
2967

2968
                        if ($scopeCloser !== $numTokens) {
3,402✔
2969
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,402✔
2970
                                $line = $this->tokens[$i]['line'];
×
2971
                                echo "\t=> token $i on line $line processed as arrow function".PHP_EOL;
×
2972
                                echo "\t\t* scope opener set to $arrow *".PHP_EOL;
×
2973
                                echo "\t\t* scope closer set to $scopeCloser *".PHP_EOL;
×
2974
                                echo "\t\t* parenthesis opener set to $x *".PHP_EOL;
×
2975
                                echo "\t\t* parenthesis closer set to $closer *".PHP_EOL;
×
2976
                            }
2977

2978
                            $this->tokens[$i]['code']            = T_FN;
3,402✔
2979
                            $this->tokens[$i]['type']            = 'T_FN';
3,402✔
2980
                            $this->tokens[$i]['scope_condition'] = $i;
3,402✔
2981
                            $this->tokens[$i]['scope_opener']    = $arrow;
3,402✔
2982
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
3,402✔
2983
                            $this->tokens[$i]['parenthesis_owner']  = $i;
3,402✔
2984
                            $this->tokens[$i]['parenthesis_opener'] = $x;
3,402✔
2985
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
3,402✔
2986

2987
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
3,402✔
2988
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
3,402✔
2989

2990
                            $this->tokens[$arrow]['scope_condition']       = $i;
3,402✔
2991
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
3,402✔
2992
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
3,402✔
2993
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
3,402✔
2994
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
3,402✔
2995
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
3,402✔
2996

2997
                            $opener = $this->tokens[$i]['parenthesis_opener'];
3,402✔
2998
                            $closer = $this->tokens[$i]['parenthesis_closer'];
3,402✔
2999
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
3,402✔
3000
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
3,402✔
3001

3002
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,402✔
3003
                                $line = $this->tokens[$arrow]['line'];
×
3004
                                echo "\t\t* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW".PHP_EOL;
×
3005
                            }
3006
                        }//end if
1,134✔
3007
                    }//end if
1,134✔
3008
                }//end if
1,134✔
3009

3010
                // If after all that, the extra tokens are not set, this is not a (valid) arrow function.
3011
                if (isset($this->tokens[$i]['scope_closer']) === false) {
3,411✔
3012
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,800✔
3013
                        $line = $this->tokens[$i]['line'];
×
3014
                        echo "\t=> token $i on line $line is not an arrow function".PHP_EOL;
×
3015
                        echo "\t\t* token changed from T_FN to T_STRING".PHP_EOL;
×
3016
                    }
3017

3018
                    $this->tokens[$i]['code'] = T_STRING;
1,800✔
3019
                    $this->tokens[$i]['type'] = 'T_STRING';
2,337✔
3020
                }
600✔
3021
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
5,007✔
3022
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
3,378✔
3023
                    continue;
297✔
3024
                }
3025

3026
                // Unless there is a variable or a bracket before this token,
3027
                // it is the start of an array being defined using the short syntax.
3028
                $isShortArray = false;
3,378✔
3029
                $allowed      = [
1,164✔
3030
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
3,378✔
3031
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
3,378✔
3032
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
3,378✔
3033
                    T_VARIABLE                 => T_VARIABLE,
3,378✔
3034
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
3,378✔
3035
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
3,378✔
3036
                    T_STRING                   => T_STRING,
3,378✔
3037
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
3,378✔
3038
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
3,378✔
3039
                ];
2,214✔
3040
                $allowed     += Tokens::$magicConstants;
3,378✔
3041

3042
                for ($x = ($i - 1); $x >= 0; $x--) {
3,378✔
3043
                    // If we hit a scope opener, the statement has ended
3044
                    // without finding anything, so it's probably an array
3045
                    // using PHP 7.1 short list syntax.
3046
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
3,378✔
3047
                        $isShortArray = true;
702✔
3048
                        break;
702✔
3049
                    }
3050

3051
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
3,378✔
3052
                        // Allow for control structures without braces.
3053
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
3,378✔
3054
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
3,378✔
3055
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
1,248✔
3056
                            || isset($allowed[$this->tokens[$x]['code']]) === false
3,378✔
3057
                        ) {
1,050✔
3058
                            $isShortArray = true;
3,378✔
3059
                        }
1,050✔
3060

3061
                        break;
3,378✔
3062
                    }
3063
                }//end for
1,050✔
3064

3065
                if ($isShortArray === true) {
3,378✔
3066
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
3,378✔
3067
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
3,378✔
3068

3069
                    $closer = $this->tokens[$i]['bracket_closer'];
3,378✔
3070
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
3,378✔
3071
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
3,378✔
3072
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,378✔
3073
                        $line = $this->tokens[$i]['line'];
×
3074
                        echo "\t* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY".PHP_EOL;
×
3075
                        $line = $this->tokens[$closer]['line'];
×
3076
                        echo "\t* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY".PHP_EOL;
×
3077
                    }
3078
                }
1,050✔
3079

3080
                continue;
3,378✔
3081
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
5,007✔
3082
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
2,331✔
3083
                    // Not a match expression after all.
3084
                    $this->tokens[$i]['code'] = T_STRING;
306✔
3085
                    $this->tokens[$i]['type'] = 'T_STRING';
306✔
3086

3087
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
306✔
3088
                        echo "\t\t* token $i changed from T_MATCH to T_STRING".PHP_EOL;
×
3089
                    }
3090

3091
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
306✔
3092
                        $opener = $this->tokens[$i]['parenthesis_opener'];
306✔
3093
                        $closer = $this->tokens[$i]['parenthesis_closer'];
306✔
3094
                        unset(
102✔
3095
                            $this->tokens[$opener]['parenthesis_owner'],
306✔
3096
                            $this->tokens[$closer]['parenthesis_owner']
306✔
3097
                        );
102✔
3098
                        unset(
102✔
3099
                            $this->tokens[$i]['parenthesis_opener'],
306✔
3100
                            $this->tokens[$i]['parenthesis_closer'],
306✔
3101
                            $this->tokens[$i]['parenthesis_owner']
306✔
3102
                        );
102✔
3103

3104
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
306✔
3105
                            echo "\t\t* cleaned parenthesis of token $i *".PHP_EOL;
102✔
3106
                        }
3107
                    }
102✔
3108
                } else {
102✔
3109
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
3110
                    $searchFor  = [
777✔
3111
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
2,331✔
3112
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
2,331✔
3113
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
2,331✔
3114
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
2,331✔
3115
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
2,331✔
3116
                    ];
1,554✔
3117
                    $searchFor += Tokens::$scopeOpeners;
2,331✔
3118

3119
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
2,331✔
3120
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
2,331✔
3121
                            continue;
2,331✔
3122
                        }
3123

3124
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
1,431✔
3125
                            $x = $this->tokens[$x]['scope_closer'];
873✔
3126
                            continue;
873✔
3127
                        }
3128

3129
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
1,431✔
3130
                            $x = $this->tokens[$x]['parenthesis_closer'];
1,431✔
3131
                            continue;
1,431✔
3132
                        }
3133

3134
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
1,431✔
3135
                            $x = $this->tokens[$x]['bracket_closer'];
873✔
3136
                            continue;
873✔
3137
                        }
3138

3139
                        // This must be a double arrow, but make sure anyhow.
3140
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,431✔
3141
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
1,431✔
3142
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
1,431✔
3143

3144
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,431✔
3145
                                echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
×
3146
                            }
3147
                        }
477✔
3148
                    }//end for
477✔
3149
                }//end if
3150

3151
                continue;
2,331✔
3152
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
5,007✔
3153
                || $this->tokens[$i]['code'] === T_BITWISE_AND
5,007✔
3154
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
5,007✔
3155
            ) {
1,593✔
3156
                if ($lastSeenTypeToken < $i) {
4,998✔
3157
                    // We've already examined this code to check if it is a type declaration and concluded it wasn't.
3158
                    // No need to do it again.
3159
                    continue;
2,547✔
3160
                }
3161

3162
                /*
3163
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3164
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3165
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3166

3167
                    All type related tokens will be converted in one go as soon as this section is hit.
3168
                */
3169

3170
                $allowed = [
1,704✔
3171
                    T_STRING       => T_STRING,
4,998✔
3172
                    T_CALLABLE     => T_CALLABLE,
4,998✔
3173
                    T_SELF         => T_SELF,
4,998✔
3174
                    T_PARENT       => T_PARENT,
4,998✔
3175
                    T_STATIC       => T_STATIC,
4,998✔
3176
                    T_FALSE        => T_FALSE,
4,998✔
3177
                    T_TRUE         => T_TRUE,
4,998✔
3178
                    T_NULL         => T_NULL,
4,998✔
3179
                    T_NAMESPACE    => T_NAMESPACE,
4,998✔
3180
                    T_NS_SEPARATOR => T_NS_SEPARATOR,
4,998✔
3181
                ];
3,294✔
3182

3183
                $suspectedType       = null;
4,998✔
3184
                $typeTokenCountAfter = 0;
4,998✔
3185

3186
                for ($x = ($i + 1); $x < $numTokens; $x++) {
4,998✔
3187
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
4,998✔
3188
                        continue;
4,998✔
3189
                    }
3190

3191
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
4,998✔
3192
                        ++$typeTokenCountAfter;
3,711✔
3193
                        continue;
3,711✔
3194
                    }
3195

3196
                    if (($typeTokenCountAfter > 0
3,408✔
3197
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
4,998✔
3198
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
4,998✔
3199
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
4,986✔
3200
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
4,986✔
3201
                    ) {
1,590✔
3202
                        // Skip past reference and variadic indicators for parameter types.
3203
                        continue;
2,736✔
3204
                    }
3205

3206
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
4,998✔
3207
                        // Parameter/Property defaults can not contain variables, so this could be a type.
3208
                        $suspectedType = 'property or parameter';
3,294✔
3209
                        break;
3,294✔
3210
                    }
3211

3212
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
4,998✔
3213
                        // Possible arrow function.
3214
                        $suspectedType = 'return';
3,402✔
3215
                        break;
3,402✔
3216
                    }
3217

3218
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
4,998✔
3219
                        // Possible abstract method or interface method.
3220
                        $suspectedType = 'return';
4,773✔
3221
                        break;
4,773✔
3222
                    }
3223

3224
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
4,998✔
3225
                        && isset($this->tokens[$x]['scope_condition']) === true
4,998✔
3226
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
4,998✔
3227
                    ) {
1,590✔
3228
                        $suspectedType = 'return';
4,248✔
3229
                        break;
4,248✔
3230
                    }
3231

3232
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
4,998✔
3233
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
3234
                        $suspectedType = 'constant';
3,063✔
3235
                        break;
3,063✔
3236
                    }
3237

3238
                    break;
4,998✔
3239
                }//end for
3240

3241
                if (($typeTokenCountAfter === 0
3,408✔
3242
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
4,998✔
3243
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
4,998✔
3244
                    || isset($suspectedType) === false
3,408✔
3245
                ) {
1,590✔
3246
                    // Definitely not a union, intersection or DNF type, move on.
3247
                    continue;
4,998✔
3248
                }
3249

3250
                if ($suspectedType === 'property or parameter') {
4,881✔
3251
                    unset($allowed[T_STATIC]);
3,294✔
3252
                }
1,098✔
3253

3254
                $typeTokenCountBefore = 0;
4,881✔
3255
                $typeOperators        = [$i];
4,881✔
3256
                $parenthesesCount     = 0;
4,881✔
3257
                $confirmed            = false;
4,881✔
3258
                $maybeNullable        = null;
4,881✔
3259

3260
                if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
4,881✔
3261
                    ++$parenthesesCount;
4,881✔
3262
                }
1,551✔
3263

3264
                for ($x = ($i - 1); $x >= 0; $x--) {
4,881✔
3265
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
4,881✔
3266
                        continue;
4,017✔
3267
                    }
3268

3269
                    if ($suspectedType === 'property or parameter'
3,330✔
3270
                        && $this->tokens[$x]['code'] === T_STRING
4,881✔
3271
                        && strtolower($this->tokens[$x]['content']) === 'static'
4,881✔
3272
                    ) {
1,551✔
3273
                        // Static keyword followed directly by an open parenthesis for a DNF type.
3274
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
3275
                        $this->tokens[$x]['code'] = T_STATIC;
900✔
3276
                        $this->tokens[$x]['type'] = 'T_STATIC';
900✔
3277

3278
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
900✔
3279
                            $line = $this->tokens[$x]['line'];
×
3280
                            echo "\t* token $x on line $line changed back from T_STRING to T_STATIC".PHP_EOL;
×
3281
                        }
3282
                    }
300✔
3283

3284
                    if ($suspectedType === 'property or parameter'
3,330✔
3285
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
4,881✔
3286
                    ) {
1,551✔
3287
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3288
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3289
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
3,294✔
3290
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
3,294✔
3291
                        ) {
1,098✔
3292
                            $confirmed = true;
3,294✔
3293
                            break;
3,294✔
3294
                        } else {
3295
                            // This may still be an arrow function which hasn't been handled yet.
3296
                            for ($y = ($x - 1); $y > 0; $y--) {
3,294✔
3297
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
3,294✔
3298
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
3,294✔
3299
                                ) {
1,098✔
3300
                                    // Non-whitespace content.
3301
                                    break;
3,294✔
3302
                                }
3303
                            }
1,098✔
3304

3305
                            if ($this->tokens[$y]['code'] === T_FN) {
3,294✔
3306
                                $confirmed = true;
2,691✔
3307
                                break;
2,691✔
3308
                            }
3309
                        }
3310
                    }//end if
1,098✔
3311

3312
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
4,881✔
3313
                        ++$typeTokenCountBefore;
4,017✔
3314
                        continue;
4,017✔
3315
                    }
3316

3317
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3318
                    if (($typeTokenCountBefore > 0
3,330✔
3319
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
4,881✔
3320
                        && ($this->tokens[$x]['code'] === T_NULLABLE
4,593✔
3321
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
4,593✔
3322
                    ) {
1,551✔
3323
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
2,550✔
3324
                            $maybeNullable = $x;
900✔
3325
                        }
300✔
3326

3327
                        continue;
2,550✔
3328
                    }
3329

3330
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
4,881✔
3331
                        $typeOperators[] = $x;
3,711✔
3332
                        continue;
3,711✔
3333
                    }
3334

3335
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
4,881✔
3336
                        ++$parenthesesCount;
4,017✔
3337
                        $typeOperators[] = $x;
4,017✔
3338
                        continue;
4,017✔
3339
                    }
3340

3341
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
4,881✔
3342
                        // Make sure this is the colon for a return type.
3343
                        for ($y = ($x - 1); $y > 0; $y--) {
2,799✔
3344
                            if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
2,799✔
3345
                                break;
2,799✔
3346
                            }
3347
                        }
933✔
3348

3349
                        if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
2,799✔
3350
                            // Definitely not a union, intersection or DNF return type, move on.
3351
                            continue 2;
900✔
3352
                        }
3353

3354
                        if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
2,799✔
3355
                            if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
2,691✔
3356
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
1,497✔
3357
                                || $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
2,094✔
3358
                            ) {
897✔
3359
                                $confirmed = true;
2,691✔
3360
                            }
897✔
3361

3362
                            break;
2,691✔
3363
                        }
3364

3365
                        // Arrow functions may not have the parenthesis_owner set correctly yet.
3366
                        // Closure use tokens won't be parentheses owners until PHPCS 4.0.
3367
                        if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
2,799✔
3368
                            for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
2,691✔
3369
                                if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
2,691✔
3370
                                    break;
2,691✔
3371
                                }
3372
                            }
711✔
3373

3374
                            if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
2,691✔
3375
                                $confirmed = true;
2,691✔
3376
                            }
897✔
3377
                        }
897✔
3378

3379
                        break;
2,799✔
3380
                    }//end if
3381

3382
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
4,881✔
3383
                        $confirmed = true;
2,658✔
3384
                        break;
2,658✔
3385
                    }
3386

3387
                    if ($suspectedType === 'property or parameter'
3,330✔
3388
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
4,128✔
3389
                        || $this->tokens[$x]['code'] === T_VAR
3,453✔
3390
                        || $this->tokens[$x]['code'] === T_STATIC
3,417✔
3391
                        || $this->tokens[$x]['code'] === T_READONLY
3,417✔
3392
                        || $this->tokens[$x]['code'] === T_FINAL)
4,206✔
3393
                    ) {
1,551✔
3394
                        // This will also confirm constructor property promotion parameters, but that's fine.
3395
                        $confirmed = true;
2,736✔
3396
                    }
912✔
3397

3398
                    break;
4,881✔
3399
                }//end for
3400

3401
                // Remember the last token we examined as part of the (non-)"type declaration".
3402
                $lastSeenTypeToken = $x;
4,881✔
3403

3404
                if ($confirmed === false
3,330✔
3405
                    && $suspectedType === 'property or parameter'
4,881✔
3406
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
4,881✔
3407
                ) {
1,551✔
3408
                    $parens = $this->tokens[$i]['nested_parenthesis'];
2,133✔
3409
                    $last   = end($parens);
2,133✔
3410

3411
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
2,133✔
3412
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
2,133✔
3413
                    ) {
711✔
3414
                        $confirmed = true;
2,133✔
3415
                    } else {
711✔
3416
                        // No parenthesis owner set, this may be an arrow function which has not yet
3417
                        // had additional processing done.
3418
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
639✔
3419
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
639✔
3420
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
639✔
3421
                                    continue;
639✔
3422
                                }
3423

3424
                                break;
639✔
3425
                            }
3426

3427
                            if ($this->tokens[$x]['code'] === T_FN) {
639✔
3428
                                for (--$x; $x >= 0; $x--) {
×
3429
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
3430
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3431
                                    ) {
3432
                                        continue;
×
3433
                                    }
3434

3435
                                    break;
×
3436
                                }
3437

3438
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
3439
                                    $confirmed = true;
×
3440
                                }
3441
                            }
3442
                        }//end if
213✔
3443
                    }//end if
3444

3445
                    unset($parens, $last);
2,133✔
3446
                }//end if
711✔
3447

3448
                if ($confirmed === false || ($parenthesesCount % 2) !== 0) {
4,881✔
3449
                    // Not a (valid) union, intersection or DNF type after all, move on.
3450
                    continue;
4,881✔
3451
                }
3452

3453
                foreach ($typeOperators as $x) {
3,603✔
3454
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
3,603✔
3455
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
3,603✔
3456
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
3,603✔
3457

3458
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,603✔
3459
                            $line = $this->tokens[$x]['line'];
×
3460
                            echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
1,239✔
3461
                        }
3462
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
3,603✔
3463
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
3,603✔
3464
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
3,603✔
3465

3466
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,603✔
3467
                            $line = $this->tokens[$x]['line'];
×
3468
                            echo "\t* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION".PHP_EOL;
1,239✔
3469
                        }
3470
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
3,603✔
3471
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
3,603✔
3472
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
3,603✔
3473

3474
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,603✔
3475
                            $line = $this->tokens[$x]['line'];
×
3476
                            echo "\t* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS".PHP_EOL;
1,239✔
3477
                        }
3478
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
3,603✔
3479
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
3,603✔
3480
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
3,603✔
3481

3482
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3,603✔
3483
                            $line = $this->tokens[$x]['line'];
×
3484
                            echo "\t* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS".PHP_EOL;
×
3485
                        }
3486
                    }//end if
1,125✔
3487
                }//end foreach
1,125✔
3488

3489
                if (isset($maybeNullable) === true) {
3,603✔
3490
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
900✔
3491
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
900✔
3492

3493
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
900✔
3494
                        $line = $this->tokens[$maybeNullable]['line'];
×
3495
                        echo "\t* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE".PHP_EOL;
×
3496
                    }
3497
                }
300✔
3498

3499
                continue;
3,603✔
3500
            } else if ($this->tokens[$i]['code'] === T_STATIC) {
5,007✔
3501
                for ($x = ($i - 1); $x > 0; $x--) {
3,918✔
3502
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
3,918✔
3503
                        break;
3,918✔
3504
                    }
3505
                }
1,230✔
3506

3507
                if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
3,918✔
3508
                    $this->tokens[$i]['code'] = T_STRING;
×
3509
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3510

3511
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3512
                        $line = $this->tokens[$i]['line'];
×
3513
                        echo "\t* token $i on line $line changed from T_STATIC to T_STRING".PHP_EOL;
×
3514
                    }
3515
                }
3516

3517
                continue;
3,918✔
3518
            } else if ($this->tokens[$i]['code'] === T_TRUE
5,007✔
3519
                || $this->tokens[$i]['code'] === T_FALSE
5,007✔
3520
                || $this->tokens[$i]['code'] === T_NULL
5,007✔
3521
            ) {
1,593✔
3522
                for ($x = ($i + 1); $x < $numTokens; $x++) {
4,755✔
3523
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
4,755✔
3524
                        // Non-whitespace content.
3525
                        break;
4,755✔
3526
                    }
3527
                }
1,326✔
3528

3529
                if ($x !== $numTokens
3,246✔
3530
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
4,755✔
3531
                ) {
1,509✔
3532
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3533
                        $line = $this->tokens[$i]['line'];
×
3534
                        $type = $this->tokens[$i]['type'];
×
3535
                        echo "\t* token $i on line $line changed from $type to T_STRING".PHP_EOL;
×
3536
                    }
3537

3538
                    $this->tokens[$i]['code'] = T_STRING;
×
3539
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3540
                }
3541
            }//end if
1,509✔
3542

3543
            if (($this->tokens[$i]['code'] !== T_CASE
5,007✔
3544
                && $this->tokens[$i]['code'] !== T_DEFAULT)
5,007✔
3545
                || isset($this->tokens[$i]['scope_opener']) === false
3,870✔
3546
            ) {
1,593✔
3547
                // Only interested in CASE and DEFAULT statements from here on in.
3548
                continue;
5,007✔
3549
            }
3550

3551
            $scopeOpener = $this->tokens[$i]['scope_opener'];
1,368✔
3552
            $scopeCloser = $this->tokens[$i]['scope_closer'];
1,368✔
3553

3554
            // If the first char after the opener is a curly brace
3555
            // and that brace has been ignored, it is actually
3556
            // opening this case statement and the opener and closer are
3557
            // probably set incorrectly.
3558
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
1,368✔
3559
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,368✔
3560
                    // Non-whitespace content.
3561
                    break;
1,368✔
3562
                }
3563
            }
456✔
3564

3565
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
1,368✔
3566
                // Special case for multiple CASE statements that share the same
3567
                // closer. Because we are going backwards through the file, this next
3568
                // CASE statement is already fixed, so just use its closer and don't
3569
                // worry about fixing anything.
3570
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3571
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3572
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3573
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3574
                    $newType = $this->tokens[$newCloser]['type'];
×
3575
                    $line    = $this->tokens[$i]['line'];
×
3576
                    echo "\t* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3577
                }
3578

3579
                continue;
×
3580
            }
3581

3582
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
1,368✔
3583
                || isset($this->tokens[$x]['scope_condition']) === true
1,368✔
3584
            ) {
456✔
3585
                // Not a CASE/DEFAULT with a curly brace opener.
3586
                continue;
1,368✔
3587
            }
3588

3589
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3590
            // not whatever it already is. The opener needs to be the opening curly
3591
            // brace so everything matches up.
3592
            $newCloser = $this->tokens[$x]['bracket_closer'];
162✔
3593
            foreach ([$i, $x, $newCloser] as $index) {
162✔
3594
                $this->tokens[$index]['scope_condition'] = $i;
162✔
3595
                $this->tokens[$index]['scope_opener']    = $x;
162✔
3596
                $this->tokens[$index]['scope_closer']    = $newCloser;
162✔
3597
            }
54✔
3598

3599
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
162✔
3600
                $line      = $this->tokens[$i]['line'];
×
3601
                $tokenType = $this->tokens[$i]['type'];
×
3602

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

3607
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3608
                $newType = $this->tokens[$newCloser]['type'];
×
3609
                echo "\t* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3610
            }
3611

3612
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
162✔
3613
                unset($this->tokens[$scopeOpener]['scope_condition']);
162✔
3614
                unset($this->tokens[$scopeOpener]['scope_opener']);
162✔
3615
                unset($this->tokens[$scopeOpener]['scope_closer']);
162✔
3616
            }
54✔
3617

3618
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
162✔
3619
                unset($this->tokens[$scopeCloser]['scope_condition']);
162✔
3620
                unset($this->tokens[$scopeCloser]['scope_opener']);
162✔
3621
                unset($this->tokens[$scopeCloser]['scope_closer']);
162✔
3622
            } else {
54✔
3623
                // We were using a shared closer. All tokens that were
3624
                // sharing this closer with us, except for the scope condition
3625
                // and it's opener, need to now point to the new closer.
3626
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3627
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3628
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3629
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3630
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3631
                    ) {
3632
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3633

3634
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3635
                            $line      = $this->tokens[$y]['line'];
×
3636
                            $tokenType = $this->tokens[$y]['type'];
×
3637
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3638
                            $newType   = $this->tokens[$newCloser]['type'];
×
3639
                            echo "\t\t* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
×
3640
                        }
3641
                    }
3642
                }
3643
            }//end if
3644

3645
            unset($this->tokens[$x]['bracket_opener']);
162✔
3646
            unset($this->tokens[$x]['bracket_closer']);
162✔
3647
            unset($this->tokens[$newCloser]['bracket_opener']);
162✔
3648
            unset($this->tokens[$newCloser]['bracket_closer']);
162✔
3649
            $this->tokens[$scopeCloser]['conditions'][] = $i;
162✔
3650

3651
            // Now fix up all the tokens that think they are
3652
            // inside the CASE/DEFAULT statement when they are really outside.
3653
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
162✔
3654
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3655
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3656
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3657
                        unset($this->tokens[$x]['conditions'][$num]);
×
3658

3659
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3660
                            $type     = $this->tokens[$x]['type'];
×
3661
                            $oldConds = '';
×
3662
                            foreach ($oldConditions as $condition) {
×
3663
                                $oldConds .= Tokens::tokenName($condition).',';
×
3664
                            }
3665

3666
                            $oldConds = rtrim($oldConds, ',');
×
3667

3668
                            $newConds = '';
×
3669
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3670
                                $newConds .= Tokens::tokenName($condition).',';
×
3671
                            }
3672

3673
                            $newConds = rtrim($newConds, ',');
×
3674

3675
                            echo "\t\t* cleaned $x ($type) *".PHP_EOL;
×
3676
                            echo "\t\t\t=> conditions changed from $oldConds to $newConds".PHP_EOL;
×
3677
                        }
3678

3679
                        break;
×
3680
                    }//end if
3681
                }//end foreach
3682
            }//end for
3683
        }//end for
54✔
3684

3685
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
5,007✔
3686
            echo "\t*** END ADDITIONAL PHP PROCESSING ***".PHP_EOL;
×
3687
        }
3688

3689
    }//end processAdditional()
3,300✔
3690

3691

3692
    /**
3693
     * Takes a token produced from <code>token_get_all()</code> and produces a
3694
     * more uniform token.
3695
     *
3696
     * @param string|array $token The token to convert.
3697
     *
3698
     * @return array The new token.
3699
     */
3700
    public static function standardiseToken($token)
9✔
3701
    {
3702
        if (isset($token[1]) === false) {
9✔
3703
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
9✔
3704
                return self::$resolveTokenCache[$token[0]];
3✔
3705
            }
3706
        } else {
3✔
3707
            $cacheKey = null;
9✔
3708
            if ($token[0] === T_STRING) {
9✔
3709
                $cacheKey = strtolower($token[1]);
9✔
3710
            } else if ($token[0] !== T_CURLY_OPEN) {
9✔
3711
                $cacheKey = $token[0];
9✔
3712
            }
3✔
3713

3714
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
9✔
3715
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3716
                $newToken['content'] = $token[1];
×
3717
                return $newToken;
×
3718
            }
3719
        }
3720

3721
        if (isset($token[1]) === false) {
9✔
3722
            return self::resolveSimpleToken($token[0]);
9✔
3723
        }
3724

3725
        if ($token[0] === T_STRING) {
9✔
3726
            switch ($cacheKey) {
3✔
3727
            case 'false':
9✔
3728
                $newToken['type'] = 'T_FALSE';
9✔
3729
                break;
9✔
3730
            case 'true':
9✔
3731
                $newToken['type'] = 'T_TRUE';
9✔
3732
                break;
9✔
3733
            case 'null':
9✔
3734
                $newToken['type'] = 'T_NULL';
9✔
3735
                break;
9✔
3736
            case 'self':
9✔
3737
                $newToken['type'] = 'T_SELF';
9✔
3738
                break;
9✔
3739
            case 'parent':
9✔
3740
                $newToken['type'] = 'T_PARENT';
9✔
3741
                break;
9✔
3742
            default:
3✔
3743
                $newToken['type'] = 'T_STRING';
9✔
3744
                break;
9✔
3745
            }
3✔
3746

3747
            $newToken['code'] = constant($newToken['type']);
9✔
3748

3749
            self::$resolveTokenCache[$cacheKey] = $newToken;
9✔
3750
        } else if ($token[0] === T_CURLY_OPEN) {
9✔
3751
            $newToken = [
3752
                'code' => T_OPEN_CURLY_BRACKET,
×
3753
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3754
            ];
3755
        } else {
3756
            $newToken = [
3✔
3757
                'code' => $token[0],
9✔
3758
                'type' => Tokens::tokenName($token[0]),
9✔
3759
            ];
6✔
3760

3761
            self::$resolveTokenCache[$token[0]] = $newToken;
9✔
3762
        }//end if
3763

3764
        $newToken['content'] = $token[1];
9✔
3765
        return $newToken;
9✔
3766

3767
    }//end standardiseToken()
3768

3769

3770
    /**
3771
     * Converts simple tokens into a format that conforms to complex tokens
3772
     * produced by token_get_all().
3773
     *
3774
     * Simple tokens are tokens that are not in array form when produced from
3775
     * token_get_all().
3776
     *
3777
     * @param string $token The simple token to convert.
3778
     *
3779
     * @return array The new token in array format.
3780
     */
3781
    public static function resolveSimpleToken($token)
9✔
3782
    {
3783
        $newToken = [];
9✔
3784

3785
        switch ($token) {
3✔
3786
        case '{':
9✔
3787
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
9✔
3788
            break;
9✔
3789
        case '}':
9✔
3790
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
9✔
3791
            break;
9✔
3792
        case '[':
9✔
3793
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
9✔
3794
            break;
9✔
3795
        case ']':
9✔
3796
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
9✔
3797
            break;
9✔
3798
        case '(':
9✔
3799
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
9✔
3800
            break;
9✔
3801
        case ')':
9✔
3802
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
9✔
3803
            break;
9✔
3804
        case ':':
9✔
3805
            $newToken['type'] = 'T_COLON';
9✔
3806
            break;
9✔
3807
        case '.':
9✔
3808
            $newToken['type'] = 'T_STRING_CONCAT';
9✔
3809
            break;
9✔
3810
        case ';':
9✔
3811
            $newToken['type'] = 'T_SEMICOLON';
9✔
3812
            break;
9✔
3813
        case '=':
9✔
3814
            $newToken['type'] = 'T_EQUAL';
9✔
3815
            break;
9✔
3816
        case '*':
9✔
3817
            $newToken['type'] = 'T_MULTIPLY';
9✔
3818
            break;
9✔
3819
        case '/':
9✔
3820
            $newToken['type'] = 'T_DIVIDE';
9✔
3821
            break;
9✔
3822
        case '+':
9✔
3823
            $newToken['type'] = 'T_PLUS';
9✔
3824
            break;
9✔
3825
        case '-':
9✔
3826
            $newToken['type'] = 'T_MINUS';
9✔
3827
            break;
9✔
3828
        case '%':
9✔
3829
            $newToken['type'] = 'T_MODULUS';
9✔
3830
            break;
9✔
3831
        case '^':
9✔
3832
            $newToken['type'] = 'T_BITWISE_XOR';
9✔
3833
            break;
9✔
3834
        case '&':
9✔
3835
            $newToken['type'] = 'T_BITWISE_AND';
6✔
3836
            break;
6✔
3837
        case '|':
9✔
3838
            $newToken['type'] = 'T_BITWISE_OR';
9✔
3839
            break;
9✔
3840
        case '~':
9✔
3841
            $newToken['type'] = 'T_BITWISE_NOT';
9✔
3842
            break;
9✔
3843
        case '<':
9✔
3844
            $newToken['type'] = 'T_LESS_THAN';
9✔
3845
            break;
9✔
3846
        case '>':
9✔
3847
            $newToken['type'] = 'T_GREATER_THAN';
9✔
3848
            break;
9✔
3849
        case '!':
9✔
3850
            $newToken['type'] = 'T_BOOLEAN_NOT';
9✔
3851
            break;
9✔
3852
        case ',':
9✔
3853
            $newToken['type'] = 'T_COMMA';
9✔
3854
            break;
9✔
3855
        case '@':
9✔
3856
            $newToken['type'] = 'T_ASPERAND';
9✔
3857
            break;
9✔
3858
        case '$':
9✔
3859
            $newToken['type'] = 'T_DOLLAR';
9✔
3860
            break;
9✔
3861
        case '`':
9✔
3862
            $newToken['type'] = 'T_BACKTICK';
9✔
3863
            break;
9✔
3864
        default:
3865
            $newToken['type'] = 'T_NONE';
×
3866
            break;
×
3867
        }//end switch
3868

3869
        $newToken['code']    = constant($newToken['type']);
9✔
3870
        $newToken['content'] = $token;
9✔
3871

3872
        self::$resolveTokenCache[$token] = $newToken;
9✔
3873
        return $newToken;
9✔
3874

3875
    }//end resolveSimpleToken()
3876

3877

3878
    /**
3879
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3880
     * Handle parenthesis balancing while searching for closing token
3881
     *
3882
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all).
3883
     * @param int             $start        The starting position.
3884
     * @param string|string[] $openerTokens The opening character.
3885
     * @param string          $closerChar   The closing character.
3886
     *
3887
     * @return int|null The position of the closing token, if found. NULL otherwise.
3888
     */
3889
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
153✔
3890
    {
3891
        $numTokens    = count($tokens);
153✔
3892
        $stack        = [0];
153✔
3893
        $closer       = null;
153✔
3894
        $openerTokens = (array) $openerTokens;
153✔
3895

3896
        for ($x = $start; $x < $numTokens; $x++) {
153✔
3897
            if (in_array($tokens[$x], $openerTokens, true) === true
153✔
3898
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
153✔
3899
            ) {
51✔
3900
                $stack[] = $x;
153✔
3901
            } else if ($tokens[$x] === $closerChar) {
153✔
3902
                array_pop($stack);
153✔
3903
                if (empty($stack) === true) {
153✔
3904
                    $closer = $x;
153✔
3905
                    break;
153✔
3906
                }
3907
            }
51✔
3908
        }
51✔
3909

3910
        return $closer;
153✔
3911

3912
    }//end findCloser()
3913

3914

3915
    /**
3916
     * PHP 8 attributes parser for PHP < 8
3917
     * Handles single-line and multiline attributes.
3918
     *
3919
     * @param array $tokens   The original array of tokens (as returned by token_get_all).
3920
     * @param int   $stackPtr The current position in token array.
3921
     *
3922
     * @return array|null The array of parsed attribute tokens
3923
     */
3924
    private function parsePhpAttribute(array &$tokens, $stackPtr)
114✔
3925
    {
3926

3927
        $token = $tokens[$stackPtr];
114✔
3928

3929
        $commentBody = substr($token[1], 2);
114✔
3930
        $subTokens   = @token_get_all('<?php '.$commentBody);
114✔
3931

3932
        foreach ($subTokens as $i => $subToken) {
114✔
3933
            if (is_array($subToken) === true
114✔
3934
                && $subToken[0] === T_COMMENT
114✔
3935
                && strpos($subToken[1], '#[') === 0
114✔
3936
            ) {
57✔
3937
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
114✔
3938
                if ($reparsed !== null) {
114✔
3939
                    array_splice($subTokens, $i, 1, $reparsed);
114✔
3940
                } else {
57✔
3941
                    $subToken[0] = T_ATTRIBUTE;
×
3942
                }
3943
            }
57✔
3944
        }
57✔
3945

3946
        array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
114✔
3947

3948
        // Go looking for the close bracket.
3949
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
114✔
3950
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
114✔
3951
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
114✔
3952
                if (is_array($token) === true) {
114✔
3953
                    $commentBody .= $token[1];
114✔
3954
                } else {
57✔
3955
                    $commentBody .= $token;
114✔
3956
                }
3957
            }
57✔
3958

3959
            $subTokens = @token_get_all('<?php '.$commentBody);
114✔
3960
            array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]);
114✔
3961

3962
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
114✔
3963
            if ($bracketCloser !== null) {
114✔
3964
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
114✔
3965
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
114✔
3966
            }
57✔
3967
        }
57✔
3968

3969
        if ($bracketCloser === null) {
114✔
3970
            return null;
114✔
3971
        }
3972

3973
        return $subTokens;
114✔
3974

3975
    }//end parsePhpAttribute()
3976

3977

3978
    /**
3979
     * Creates a map for the attributes tokens that surround other tokens.
3980
     *
3981
     * @return void
3982
     */
3983
    private function createAttributesNestingMap()
9✔
3984
    {
3985
        $map = [];
9✔
3986
        for ($i = 0; $i < $this->numTokens; $i++) {
9✔
3987
            if (isset($this->tokens[$i]['attribute_opener']) === true
9✔
3988
                && $i === $this->tokens[$i]['attribute_opener']
9✔
3989
            ) {
3✔
3990
                if (empty($map) === false) {
9✔
3991
                    $this->tokens[$i]['nested_attributes'] = $map;
9✔
3992
                }
3✔
3993

3994
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
9✔
3995
                    $map[$this->tokens[$i]['attribute_opener']]
9✔
3996
                        = $this->tokens[$i]['attribute_closer'];
9✔
3997
                }
3✔
3998
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
9✔
3999
                && $i === $this->tokens[$i]['attribute_closer']
9✔
4000
            ) {
3✔
4001
                array_pop($map);
9✔
4002
                if (empty($map) === false) {
9✔
4003
                    $this->tokens[$i]['nested_attributes'] = $map;
9✔
4004
                }
3✔
4005
            } else {
3✔
4006
                if (empty($map) === false) {
9✔
4007
                    $this->tokens[$i]['nested_attributes'] = $map;
9✔
4008
                }
3✔
4009
            }//end if
4010
        }//end for
3✔
4011

4012
    }//end createAttributesNestingMap()
6✔
4013

4014

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

© 2025 Coveralls, Inc