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

PHPCSStandards / PHP_CodeSniffer / 8867362179

28 Apr 2024 11:52AM UTC coverage: 73.16% (+0.09%) from 73.069%
8867362179

push

github

jrfnl
PHP 8.2 | Tokenizer/PHP: add support for DNF types

This commit adds tokenizer support for DNF types as per the proposal outlined in 387.

This means that:
* Two new tokens are introduced `T_TYPE_OPEN_PARENTHESIS` and `T_TYPE_CLOSE_PARENTHESIS` for the parentheses used in DNF types.
    This allows for sniffs to specifically target those tokens and prevents sniffs which are looking for the "normal" open/close parenthesis tokens from acting on DNF parentheses.
* These new tokens, like other parentheses, will get the `parenthesis_opener` and `parenthesis_closer` token array indexes and the tokens between them will have the `nested_parenthesis` index.

Based on the currently added tests, the commit safeguards that:
* The `|` in types is still tokenized as `T_TYPE_UNION`, even in DNF types.
* The `&` in types is still tokenized as `T_TYPE_INTERSECTION`, even in DNF types.
* The `static` keyword for properties is still tokenized as `T_STATIC`, even when right before a DNF type (which could be confused for a function call).
* The arrow function retokenization to `T_FN` with a `T_FN_ARROW` scope opener is handled correctly, even when DNF types are involved and including when the arrow function is declared to return by reference.
* The keyword tokens, like `self`, `parent`, `static`, `true` or `false`, when used in DNF types are still tokenized to their own token and not tokenized as `T_STRING`.
* The `array` keyword when used in DNF types is still tokenized as `T_STRING` and not as `T_ARRAY`.
* A `?` intended as an (illegal) nullability operator in combination with a DNF type is still tokenized as `T_NULLABLE` and not as `T_INLINE_THEN`.
* A function declaration open parenthesis before a typed parameter isn't accidentally retokenized to `T_TYPE_OPEN_PARENTHESIS`.

Includes ample unit tests.

Even so, strenuous testing of this PR is recommended as there are so many moving parts involved, it is very easy for something to have been overlooked.

Related to 105

... (continued)

79 of 86 new or added lines in 1 file covered. (91.86%)

7 existing lines in 1 file now uncovered.

17764 of 24281 relevant lines covered (73.16%)

67.5 hits per line

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

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

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

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

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

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

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

511

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

532
        $tokens      = @token_get_all($string);
2,898✔
533
        $finalTokens = [];
2,898✔
534

535
        $newStackPtr       = 0;
2,898✔
536
        $numTokens         = count($tokens);
2,898✔
537
        $lastNotEmptyToken = 0;
2,898✔
538

539
        $insideInlineIf         = [];
2,898✔
540
        $insideUseGroup         = false;
2,898✔
541
        $insideConstDeclaration = false;
2,898✔
542

543
        $commentTokenizer = new Comment();
2,898✔
544

545
        for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
2,898✔
546
            // Special case for tokens we have needed to blank out.
547
            if ($tokens[$stackPtr] === null) {
2,898✔
548
                continue;
1,204✔
549
            }
550

551
            $token        = (array) $tokens[$stackPtr];
2,898✔
552
            $tokenIsArray = isset($token[1]);
2,898✔
553

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

564
                $statusMessage = 'Process token ';
×
565
                if ($tokenIsArray === true) {
×
566
                    $statusMessage .= "[$stackPtr]";
×
567
                } else {
568
                    $statusMessage .= " $stackPtr ";
×
569
                }
570

571
                $statusMessage .= ": $type => $content";
×
572
                Common::printStatusMessage($statusMessage, 1, true);
×
573
            }//end if
574

575
            if ($newStackPtr > 0
2,898✔
576
                && isset(Tokens::$emptyTokens[$finalTokens[($newStackPtr - 1)]['code']]) === false
2,898✔
577
            ) {
578
                $lastNotEmptyToken = ($newStackPtr - 1);
2,898✔
579
            }
580

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

588
            if ($tokenIsArray === true && substr($token[1], -1) === "\r") {
2,898✔
589
                if (isset($tokens[($stackPtr + 1)]) === true
×
590
                    && is_array($tokens[($stackPtr + 1)]) === true
×
591
                    && $tokens[($stackPtr + 1)][1][0] === "\n"
×
592
                ) {
593
                    $token[1] .= "\n";
×
594
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
595
                        if ($isWin === true) {
×
596
                            Common::printStatusMessage('\n', 0, true);
×
597
                        } else {
598
                            Common::printStatusMessage("\033[30;1m\\n\033[0m", 0, true);
×
599
                        }
600
                    }
601

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

612
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,898✔
613
                Common::printStatusMessage(PHP_EOL, 0, true);
×
614
            }
615

616
            /*
617
                Tokenize context sensitive keyword as string when it should be string.
618
            */
619

620
            if ($tokenIsArray === true
2,898✔
621
                && isset(Tokens::$contextSensitiveKeywords[$token[0]]) === true
2,898✔
622
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,803✔
623
                || $finalTokens[$lastNotEmptyToken]['content'] === '&'
2,803✔
624
                || $insideConstDeclaration === true)
2,898✔
625
            ) {
626
                if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
2,324✔
627
                    $preserveKeyword = false;
2,324✔
628

629
                    // `new class`, and `new static` should be preserved.
630
                    if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,324✔
631
                        && ($token[0] === T_CLASS
2,089✔
632
                        || $token[0] === T_STATIC)
2,324✔
633
                    ) {
634
                        $preserveKeyword = true;
1,512✔
635
                    }
636

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

649
                        if (is_array($tokens[$i]) === true && $tokens[$i][0] === T_CLASS) {
340✔
650
                            $preserveKeyword = true;
340✔
651
                        }
652
                    }
653

654
                    // `new class extends` `new class implements` should be preserved
655
                    if (($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS)
2,324✔
656
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CLASS
2,324✔
657
                    ) {
658
                        $preserveKeyword = true;
510✔
659
                    }
660

661
                    // `namespace\` should be preserved
662
                    if ($token[0] === T_NAMESPACE) {
2,324✔
663
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
620✔
664
                            if (is_array($tokens[$i]) === false) {
620✔
665
                                break;
510✔
666
                            }
667

668
                            if (isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true) {
620✔
669
                                continue;
510✔
670
                            }
671

672
                            if ($tokens[$i][0] === T_NS_SEPARATOR) {
280✔
673
                                $preserveKeyword = true;
280✔
674
                            }
675

676
                            break;
280✔
677
                        }
678
                    }
679
                }//end if
680

681
                // Types in typed constants should not be touched, but the constant name should be.
682
                if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,324✔
683
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,324✔
684
                    || $insideConstDeclaration === true
2,324✔
685
                ) {
686
                    $preserveKeyword = true;
2,186✔
687

688
                    // Find the next non-empty token.
689
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,186✔
690
                        if (is_array($tokens[$i]) === true
2,186✔
691
                            && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,186✔
692
                        ) {
693
                            continue;
1,547✔
694
                        }
695

696
                        break;
2,186✔
697
                    }
698

699
                    if ($tokens[$i] === '=' || $tokens[$i] === ';') {
2,186✔
700
                        $preserveKeyword        = false;
737✔
701
                        $insideConstDeclaration = false;
737✔
702
                    }
703
                }//end if
704

705
                if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,324✔
706
                    $preserveKeyword = true;
573✔
707

708
                    for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
573✔
709
                        if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
573✔
710
                            continue;
510✔
711
                        }
712

713
                        if ($finalTokens[$i]['code'] === T_FUNCTION) {
573✔
714
                            $preserveKeyword = false;
510✔
715
                        }
716

717
                        break;
573✔
718
                    }
719
                }
720

721
                if ($preserveKeyword === false) {
2,324✔
722
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
958✔
723
                        $type = Tokens::tokenName($token[0]);
×
724
                        Common::printStatusMessage("* token $stackPtr changed from $type to T_STRING", 2);
×
725
                    }
726

727
                    $finalTokens[$newStackPtr] = [
958✔
728
                        'code'    => T_STRING,
958✔
729
                        'type'    => 'T_STRING',
958✔
730
                        'content' => $token[1],
958✔
731
                    ];
666✔
732

733
                    $newStackPtr++;
958✔
734
                    continue;
958✔
735
                }
736
            }//end if
737

738
            /*
739
                Mark the start of a constant declaration to allow for handling keyword to T_STRING
740
                convertion for constant names using reserved keywords.
741
            */
742

743
            if ($tokenIsArray === true && $token[0] === T_CONST) {
2,898✔
744
                $insideConstDeclaration = true;
2,373✔
745
            }
746

747
            /*
748
                Close an open "inside constant declaration" marker when no keyword convertion was needed.
749
            */
750

751
            if ($insideConstDeclaration === true
2,898✔
752
                && $tokenIsArray === false
2,898✔
753
                && ($token[0] === '=' || $token[0] === ';')
2,898✔
754
            ) {
755
                $insideConstDeclaration = false;
950✔
756
            }
757

758
            /*
759
                Special case for `static` used as a function name, i.e. `static()`.
760

761
                Note: this may incorrectly change the static keyword directly before a DNF property type.
762
                If so, this will be caught and corrected for in the additional processing.
763
            */
764

765
            if ($tokenIsArray === true
2,898✔
766
                && $token[0] === T_STATIC
2,898✔
767
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW
2,898✔
768
            ) {
769
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,043✔
770
                    if (is_array($tokens[$i]) === true
2,043✔
771
                        && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,043✔
772
                    ) {
773
                        continue;
1,404✔
774
                    }
775

776
                    if ($tokens[$i][0] === '(') {
2,043✔
777
                        $finalTokens[$newStackPtr] = [
510✔
778
                            'code'    => T_STRING,
510✔
779
                            'type'    => 'T_STRING',
510✔
780
                            'content' => $token[1],
510✔
781
                        ];
340✔
782

783
                        $newStackPtr++;
510✔
784
                        continue 2;
510✔
785
                    }
786

787
                    break;
2,043✔
788
                }
789
            }//end if
790

791
            /*
792
                Parse doc blocks into something that can be easily iterated over.
793
            */
794

795
            if ($tokenIsArray === true
2,898✔
796
                && ($token[0] === T_DOC_COMMENT
2,898✔
797
                || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0))
2,898✔
798
            ) {
799
                $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
90✔
800
                foreach ($commentTokens as $commentToken) {
90✔
801
                    $finalTokens[$newStackPtr] = $commentToken;
90✔
802
                    $newStackPtr++;
90✔
803
                }
804

805
                continue;
90✔
806
            }
807

808
            /*
809
                PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token.
810
            */
811

812
            if (PHP_VERSION_ID >= 80000
2,898✔
813
                && $tokenIsArray === true
2,898✔
814
                && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0))
2,898✔
815
                && isset($tokens[($stackPtr + 1)]) === true
2,898✔
816
                && is_array($tokens[($stackPtr + 1)]) === true
2,898✔
817
                && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
2,898✔
818
            ) {
819
                $nextToken = $tokens[($stackPtr + 1)];
1,468✔
820

821
                // If the next token is a single new line, merge it into the comment token
822
                // and set to it up to be skipped.
823
                if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") {
1,468✔
824
                    $token[1] .= $nextToken[1];
1,204✔
825
                    $tokens[($stackPtr + 1)] = null;
1,204✔
826

827
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,204✔
828
                        Common::printStatusMessage("* merged newline after comment into comment token $stackPtr", 2);
602✔
829
                    }
830
                } else {
831
                    // This may be a whitespace token consisting of multiple new lines.
832
                    if (strpos($nextToken[1], "\r\n") === 0) {
1,134✔
833
                        $token[1] .= "\r\n";
30✔
834
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
30✔
835
                    } else if (strpos($nextToken[1], "\n\r") === 0) {
1,104✔
836
                        $token[1] .= "\n\r";
×
837
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2);
×
838
                    } else if (strpos($nextToken[1], "\n") === 0) {
1,104✔
839
                        $token[1] .= "\n";
1,104✔
840
                        $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1);
1,104✔
841
                    }
842

843
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,134✔
844
                        Common::printStatusMessage("* stripped first newline after comment and added it to comment token $stackPtr", 2);
×
845
                    }
846
                }//end if
847
            }//end if
848

849
            /*
850
                For Explicit Octal Notation prior to PHP 8.1 we need to combine the
851
                T_LNUMBER and T_STRING token values into a single token value, and
852
                then ignore the T_STRING token.
853
            */
854

855
            if (PHP_VERSION_ID < 80100
2,898✔
856
                && $tokenIsArray === true && $token[1] === '0'
2,898✔
857
                && (isset($tokens[($stackPtr + 1)]) === true
2,484✔
858
                && is_array($tokens[($stackPtr + 1)]) === true
2,484✔
859
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,484✔
860
                && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true
2,484✔
861
                && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
2,484✔
862
                && $tokens[($stackPtr + 1)][1][1] !== '_')
2,898✔
863
                && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
2,898✔
864
            ) {
865
                $finalTokens[$newStackPtr] = [
37✔
866
                    'code'    => T_LNUMBER,
37✔
867
                    'type'    => 'T_LNUMBER',
37✔
868
                    'content' => $token[1] .= $matches[1],
37✔
869
                ];
870
                $newStackPtr++;
37✔
871

872
                if (isset($matches[2]) === true && $matches[2] !== '') {
37✔
873
                    $type = 'T_LNUMBER';
10✔
874
                    if ($matches[2][0] === '_') {
10✔
875
                        $type = 'T_STRING';
10✔
876
                    }
877

878
                    $finalTokens[$newStackPtr] = [
10✔
879
                        'code'    => constant($type),
10✔
880
                        'type'    => $type,
10✔
881
                        'content' => $matches[2],
10✔
882
                    ];
883
                    $newStackPtr++;
10✔
884
                }
885

886
                $stackPtr++;
37✔
887
                continue;
37✔
888
            }//end if
889

890
            /*
891
                PHP 8.1 introduced two dedicated tokens for the & character.
892
                Retokenizing both of these to T_BITWISE_AND, which is the
893
                token PHPCS already tokenized them as.
894
            */
895

896
            if ($tokenIsArray === true
2,898✔
897
                && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
2,898✔
898
                || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG)
2,898✔
899
            ) {
900
                $finalTokens[$newStackPtr] = [
824✔
901
                    'code'    => T_BITWISE_AND,
824✔
902
                    'type'    => 'T_BITWISE_AND',
824✔
903
                    'content' => $token[1],
824✔
904
                ];
824✔
905
                $newStackPtr++;
824✔
906
                continue;
824✔
907
            }
908

909
            /*
910
                If this is a double quoted string, PHP will tokenize the whole
911
                thing which causes problems with the scope map when braces are
912
                within the string. So we need to merge the tokens together to
913
                provide a single string.
914
            */
915

916
            if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
2,898✔
917
                // Binary casts need a special token.
918
                if ($token[0] === 'b"') {
93✔
919
                    $finalTokens[$newStackPtr] = [
×
920
                        'code'    => T_BINARY_CAST,
×
921
                        'type'    => 'T_BINARY_CAST',
×
922
                        'content' => 'b',
×
923
                    ];
924
                    $newStackPtr++;
×
925
                }
926

927
                $tokenContent = '"';
93✔
928
                $nestedVars   = [];
93✔
929
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
93✔
930
                    $subToken        = (array) $tokens[$i];
93✔
931
                    $subTokenIsArray = isset($subToken[1]);
93✔
932

933
                    if ($subTokenIsArray === true) {
93✔
934
                        $tokenContent .= $subToken[1];
93✔
935
                        if (($subToken[1] === '{'
93✔
936
                            || $subToken[1] === '${')
93✔
937
                            && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
93✔
938
                        ) {
939
                            $nestedVars[] = $i;
82✔
940
                        }
941
                    } else {
942
                        $tokenContent .= $subToken[0];
93✔
943
                        if ($subToken[0] === '}') {
93✔
944
                            array_pop($nestedVars);
60✔
945
                        }
946
                    }
947

948
                    if ($subTokenIsArray === false
93✔
949
                        && $subToken[0] === '"'
93✔
950
                        && empty($nestedVars) === true
93✔
951
                    ) {
952
                        // We found the other end of the double quoted string.
953
                        break;
93✔
954
                    }
955
                }//end for
956

957
                $stackPtr = $i;
93✔
958

959
                // Convert each line within the double quoted string to a
960
                // new token, so it conforms with other multiple line tokens.
961
                $tokenLines = explode($this->eolChar, $tokenContent);
93✔
962
                $numLines   = count($tokenLines);
93✔
963
                $newToken   = [];
93✔
964

965
                for ($j = 0; $j < $numLines; $j++) {
93✔
966
                    $newToken['content'] = $tokenLines[$j];
93✔
967
                    if ($j === ($numLines - 1)) {
93✔
968
                        if ($tokenLines[$j] === '') {
93✔
969
                            break;
82✔
970
                        }
971
                    } else {
972
                        $newToken['content'] .= $this->eolChar;
60✔
973
                    }
974

975
                    $newToken['code']          = T_DOUBLE_QUOTED_STRING;
93✔
976
                    $newToken['type']          = 'T_DOUBLE_QUOTED_STRING';
93✔
977
                    $finalTokens[$newStackPtr] = $newToken;
93✔
978
                    $newStackPtr++;
93✔
979
                }
980

981
                // Continue, as we're done with this token.
982
                continue;
93✔
983
            }//end if
984

985
            /*
986
                Detect binary casting and assign the casts their own token.
987
            */
988

989
            if ($tokenIsArray === true
2,898✔
990
                && $token[0] === T_CONSTANT_ENCAPSED_STRING
2,898✔
991
                && (substr($token[1], 0, 2) === 'b"'
2,803✔
992
                || substr($token[1], 0, 2) === "b'")
2,898✔
993
            ) {
994
                $finalTokens[$newStackPtr] = [
×
995
                    'code'    => T_BINARY_CAST,
×
996
                    'type'    => 'T_BINARY_CAST',
×
997
                    'content' => 'b',
×
998
                ];
999
                $newStackPtr++;
×
1000
                $token[1] = substr($token[1], 1);
×
1001
            }
1002

1003
            if ($tokenIsArray === true
2,898✔
1004
                && $token[0] === T_STRING_CAST
2,898✔
1005
                && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
2,898✔
1006
            ) {
1007
                $finalTokens[$newStackPtr] = [
×
1008
                    'code'    => T_BINARY_CAST,
×
1009
                    'type'    => 'T_BINARY_CAST',
×
1010
                    'content' => $token[1],
×
1011
                ];
1012
                $newStackPtr++;
×
1013
                continue;
×
1014
            }
1015

1016
            /*
1017
                If this is a heredoc, PHP will tokenize the whole
1018
                thing which causes problems when heredocs don't
1019
                contain real PHP code, which is almost never.
1020
                We want to leave the start and end heredoc tokens
1021
                alone though.
1022
            */
1023

1024
            if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
2,898✔
1025
                // Add the start heredoc token to the final array.
1026
                $finalTokens[$newStackPtr] = self::standardiseToken($token);
114✔
1027

1028
                // Check if this is actually a nowdoc and use a different token
1029
                // to help the sniffs.
1030
                $nowdoc = false;
114✔
1031
                if (strpos($token[1], "'") !== false) {
114✔
1032
                    $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
×
1033
                    $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
×
1034
                    $nowdoc = true;
×
1035
                }
1036

1037
                $tokenContent = '';
114✔
1038
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
114✔
1039
                    $subTokenIsArray = is_array($tokens[$i]);
114✔
1040
                    if ($subTokenIsArray === true
114✔
1041
                        && $tokens[$i][0] === T_END_HEREDOC
114✔
1042
                    ) {
1043
                        // We found the other end of the heredoc.
1044
                        break;
114✔
1045
                    }
1046

1047
                    if ($subTokenIsArray === true) {
114✔
1048
                        $tokenContent .= $tokens[$i][1];
114✔
1049
                    } else {
1050
                        $tokenContent .= $tokens[$i];
114✔
1051
                    }
1052
                }
1053

1054
                if ($i === $numTokens) {
114✔
1055
                    // We got to the end of the file and never
1056
                    // found the closing token, so this probably wasn't
1057
                    // a heredoc.
1058
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1059
                        $type = $finalTokens[$newStackPtr]['type'];
×
1060
                        Common::printStatusMessage('* failed to find the end of the here/nowdoc', 2);
×
1061
                        Common::printStatusMessage("* token $stackPtr changed from $type to T_STRING", 2);
×
1062
                    }
1063

1064
                    $finalTokens[$newStackPtr]['code'] = T_STRING;
×
1065
                    $finalTokens[$newStackPtr]['type'] = 'T_STRING';
×
1066
                    $newStackPtr++;
×
1067
                    continue;
×
1068
                }
1069

1070
                $stackPtr = $i;
114✔
1071
                $newStackPtr++;
114✔
1072

1073
                // Convert each line within the heredoc to a
1074
                // new token, so it conforms with other multiple line tokens.
1075
                $tokenLines = explode($this->eolChar, $tokenContent);
114✔
1076
                $numLines   = count($tokenLines);
114✔
1077
                $newToken   = [];
114✔
1078

1079
                for ($j = 0; $j < $numLines; $j++) {
114✔
1080
                    $newToken['content'] = $tokenLines[$j];
114✔
1081
                    if ($j === ($numLines - 1)) {
114✔
1082
                        if ($tokenLines[$j] === '') {
114✔
1083
                            break;
114✔
1084
                        }
1085
                    } else {
1086
                        $newToken['content'] .= $this->eolChar;
114✔
1087
                    }
1088

1089
                    if ($nowdoc === true) {
114✔
1090
                        $newToken['code'] = T_NOWDOC;
×
1091
                        $newToken['type'] = 'T_NOWDOC';
×
1092
                    } else {
1093
                        $newToken['code'] = T_HEREDOC;
114✔
1094
                        $newToken['type'] = 'T_HEREDOC';
114✔
1095
                    }
1096

1097
                    $finalTokens[$newStackPtr] = $newToken;
114✔
1098
                    $newStackPtr++;
114✔
1099
                }//end for
1100

1101
                // Add the end heredoc token to the final array.
1102
                $finalTokens[$newStackPtr] = self::standardiseToken($tokens[$stackPtr]);
114✔
1103

1104
                if ($nowdoc === true) {
114✔
1105
                    $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
×
1106
                    $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
×
1107
                }
1108

1109
                $newStackPtr++;
114✔
1110

1111
                // Continue, as we're done with this token.
1112
                continue;
114✔
1113
            }//end if
1114

1115
            /*
1116
                Enum keyword for PHP < 8.1
1117
            */
1118

1119
            if ($tokenIsArray === true
2,898✔
1120
                && $token[0] === T_STRING
2,898✔
1121
                && strtolower($token[1]) === 'enum'
2,898✔
1122
            ) {
1123
                // Get the next non-empty token.
1124
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,352✔
1125
                    if (is_array($tokens[$i]) === false
1,352✔
1126
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,352✔
1127
                    ) {
1128
                        break;
1,352✔
1129
                    }
1130
                }
1131

1132
                if (isset($tokens[$i]) === true
1,352✔
1133
                    && is_array($tokens[$i]) === true
1,352✔
1134
                    && $tokens[$i][0] === T_STRING
1,352✔
1135
                ) {
1136
                    // Modify $tokens directly so we can use it later when converting enum "case".
1137
                    $tokens[$stackPtr][0] = T_ENUM;
309✔
1138

1139
                    $newToken            = [];
309✔
1140
                    $newToken['code']    = T_ENUM;
309✔
1141
                    $newToken['type']    = 'T_ENUM';
309✔
1142
                    $newToken['content'] = $token[1];
309✔
1143
                    $finalTokens[$newStackPtr] = $newToken;
309✔
1144

1145
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
309✔
1146
                        Common::printStatusMessage("* token $stackPtr changed from T_STRING to T_ENUM", 2);
×
1147
                    }
1148

1149
                    $newStackPtr++;
309✔
1150
                    continue;
309✔
1151
                }
1152
            }//end if
1153

1154
            /*
1155
                Convert enum "case" to T_ENUM_CASE
1156
            */
1157

1158
            if ($tokenIsArray === true
2,898✔
1159
                && $token[0] === T_CASE
2,898✔
1160
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,898✔
1161
            ) {
1162
                $isEnumCase = false;
1,521✔
1163
                $scope      = 1;
1,521✔
1164

1165
                for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,521✔
1166
                    if ($tokens[$i] === '}') {
1,521✔
1167
                        $scope++;
1,488✔
1168
                        continue;
1,488✔
1169
                    }
1170

1171
                    if ($tokens[$i] === '{') {
1,521✔
1172
                        $scope--;
1,521✔
1173
                        continue;
1,521✔
1174
                    }
1175

1176
                    if (is_array($tokens[$i]) === false) {
1,521✔
1177
                        continue;
1,521✔
1178
                    }
1179

1180
                    if ($scope !== 0) {
1,521✔
1181
                        continue;
1,521✔
1182
                    }
1183

1184
                    if ($tokens[$i][0] === T_SWITCH) {
1,011✔
1185
                        break;
945✔
1186
                    }
1187

1188
                    if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
1,011✔
1189
                        $isEnumCase = true;
129✔
1190
                        break;
129✔
1191
                    }
1192
                }//end for
1193

1194
                if ($isEnumCase === true) {
1,521✔
1195
                    // Modify $tokens directly so we can use it as optimisation for other enum "case".
1196
                    $tokens[$stackPtr][0] = T_ENUM_CASE;
129✔
1197

1198
                    $newToken            = [];
129✔
1199
                    $newToken['code']    = T_ENUM_CASE;
129✔
1200
                    $newToken['type']    = 'T_ENUM_CASE';
129✔
1201
                    $newToken['content'] = $token[1];
129✔
1202
                    $finalTokens[$newStackPtr] = $newToken;
129✔
1203

1204
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
129✔
1205
                        Common::printStatusMessage("* token $stackPtr changed from T_CASE to T_ENUM_CASE", 2);
×
1206
                    }
1207

1208
                    $newStackPtr++;
129✔
1209
                    continue;
129✔
1210
                }
1211
            }//end if
1212

1213
            /*
1214
                PHP 8.0 Attributes
1215
            */
1216

1217
            if (PHP_VERSION_ID < 80000
2,898✔
1218
                && $token[0] === T_COMMENT
2,898✔
1219
                && strpos($token[1], '#[') === 0
2,898✔
1220
            ) {
1221
                $subTokens = $this->parsePhpAttribute($tokens, $stackPtr);
17✔
1222
                if ($subTokens !== null) {
17✔
1223
                    array_splice($tokens, $stackPtr, 1, $subTokens);
17✔
1224
                    $numTokens = count($tokens);
17✔
1225

1226
                    $tokenIsArray = true;
17✔
1227
                    $token        = $tokens[$stackPtr];
17✔
1228
                } else {
1229
                    $token[0] = T_ATTRIBUTE;
17✔
1230
                }
1231
            }
1232

1233
            if ($tokenIsArray === true
2,898✔
1234
                && $token[0] === T_ATTRIBUTE
2,898✔
1235
            ) {
1236
                // Go looking for the close bracket.
1237
                $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']');
51✔
1238

1239
                $newToken            = [];
51✔
1240
                $newToken['code']    = T_ATTRIBUTE;
51✔
1241
                $newToken['type']    = 'T_ATTRIBUTE';
51✔
1242
                $newToken['content'] = '#[';
51✔
1243
                $finalTokens[$newStackPtr] = $newToken;
51✔
1244

1245
                $tokens[$bracketCloser]    = [];
51✔
1246
                $tokens[$bracketCloser][0] = T_ATTRIBUTE_END;
51✔
1247
                $tokens[$bracketCloser][1] = ']';
51✔
1248

1249
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
51✔
1250
                    Common::printStatusMessage("* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END", 2);
×
1251
                }
1252

1253
                $newStackPtr++;
51✔
1254
                continue;
51✔
1255
            }//end if
1256

1257
            /*
1258
                Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
1259
                token and ensures that the colon after it is always T_COLON.
1260
            */
1261

1262
            if ($tokenIsArray === true
2,898✔
1263
                && ($token[0] === T_STRING
2,898✔
1264
                || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1)
2,898✔
1265
            ) {
1266
                // Get the next non-empty token.
1267
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,724✔
1268
                    if (is_array($tokens[$i]) === false
2,724✔
1269
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
2,724✔
1270
                    ) {
1271
                        break;
2,724✔
1272
                    }
1273
                }
1274

1275
                if (isset($tokens[$i]) === true
2,724✔
1276
                    && is_array($tokens[$i]) === false
2,724✔
1277
                    && $tokens[$i] === ':'
2,724✔
1278
                ) {
1279
                    // Get the previous non-empty token.
1280
                    for ($j = ($stackPtr - 1); $j > 0; $j--) {
1,593✔
1281
                        if (is_array($tokens[$j]) === false
1,593✔
1282
                            || isset(Tokens::$emptyTokens[$tokens[$j][0]]) === false
1,593✔
1283
                        ) {
1284
                            break;
1,593✔
1285
                        }
1286
                    }
1287

1288
                    if (is_array($tokens[$j]) === false
1,593✔
1289
                        && ($tokens[$j] === '('
1,543✔
1290
                        || $tokens[$j] === ',')
1,593✔
1291
                    ) {
1292
                        $newToken            = [];
690✔
1293
                        $newToken['code']    = T_PARAM_NAME;
690✔
1294
                        $newToken['type']    = 'T_PARAM_NAME';
690✔
1295
                        $newToken['content'] = $token[1];
690✔
1296
                        $finalTokens[$newStackPtr] = $newToken;
690✔
1297

1298
                        $newStackPtr++;
690✔
1299

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

1304
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
690✔
1305
                            $type = Tokens::tokenName($token[0]);
×
1306
                            Common::printStatusMessage("* token $stackPtr changed from $type to T_PARAM_NAME", 2);
×
1307
                        }
1308

1309
                        continue;
690✔
1310
                    }
1311
                }//end if
1312
            }//end if
1313

1314
            /*
1315
                "readonly" keyword for PHP < 8.1
1316
            */
1317

1318
            if ($tokenIsArray === true
2,898✔
1319
                && strtolower($token[1]) === 'readonly'
2,898✔
1320
                && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,262✔
1321
                || $finalTokens[$lastNotEmptyToken]['code'] === T_NEW)
2,898✔
1322
            ) {
1323
                // Get the next non-whitespace token.
1324
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
990✔
1325
                    if (is_array($tokens[$i]) === false
990✔
1326
                        || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
990✔
1327
                    ) {
1328
                        break;
990✔
1329
                    }
1330
                }
1331

1332
                $isReadonlyKeyword = false;
990✔
1333

1334
                if (isset($tokens[$i]) === false
990✔
1335
                    || $tokens[$i] !== '('
990✔
1336
                ) {
1337
                    $isReadonlyKeyword = true;
990✔
1338
                } else if ($tokens[$i] === '(') {
×
1339
                    /*
1340
                     * Skip over tokens which can be used in type declarations.
1341
                     * At this point, the only token types which need to be taken into consideration
1342
                     * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR
1343
                     * and the union/intersection/dnf parentheses.
1344
                     */
1345

1346
                    $foundDNFParens = 1;
×
1347
                    $foundDNFPipe   = 0;
×
1348

1349
                    for (++$i; $i < $numTokens; $i++) {
×
1350
                        if (is_array($tokens[$i]) === true) {
×
1351
                            $tokenType = $tokens[$i][0];
×
1352
                        } else {
1353
                            $tokenType = $tokens[$i];
×
1354
                        }
1355

1356
                        if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
×
1357
                            continue;
×
1358
                        }
1359

1360
                        if ($tokenType === '|') {
×
1361
                            ++$foundDNFPipe;
×
1362
                            continue;
×
1363
                        }
1364

1365
                        if ($tokenType === ')') {
×
1366
                            ++$foundDNFParens;
×
1367
                            continue;
×
1368
                        }
1369

1370
                        if ($tokenType === '(') {
×
1371
                            ++$foundDNFParens;
×
1372
                            continue;
×
1373
                        }
1374

1375
                        if ($tokenType === T_STRING
×
1376
                            || $tokenType === T_NAME_FULLY_QUALIFIED
×
1377
                            || $tokenType === T_NAME_RELATIVE
×
1378
                            || $tokenType === T_NAME_QUALIFIED
×
1379
                            || $tokenType === T_ARRAY
×
1380
                            || $tokenType === T_NAMESPACE
×
1381
                            || $tokenType === T_NS_SEPARATOR
×
1382
                            || $tokenType === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG // PHP 8.0+.
×
1383
                            || $tokenType === '&' // PHP < 8.0.
×
1384
                        ) {
1385
                            continue;
×
1386
                        }
1387

1388
                        // Reached the next token after.
1389
                        if (($foundDNFParens % 2) === 0
×
1390
                            && $foundDNFPipe >= 1
×
1391
                            && ($tokenType === T_VARIABLE
×
1392
                            || $tokenType === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG)
×
1393
                        ) {
1394
                            $isReadonlyKeyword = true;
×
1395
                        }
1396

1397
                        break;
×
1398
                    }//end for
1399
                }//end if
1400

1401
                if ($isReadonlyKeyword === true) {
990✔
1402
                    $finalTokens[$newStackPtr] = [
990✔
1403
                        'code'    => T_READONLY,
990✔
1404
                        'type'    => 'T_READONLY',
990✔
1405
                        'content' => $token[1],
990✔
1406
                    ];
660✔
1407
                    $newStackPtr++;
990✔
1408

1409
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_READONLY) {
990✔
1410
                        Common::printStatusMessage("* token $stackPtr changed from $type to T_READONLY", 2);
660✔
1411
                    }
1412
                } else {
1413
                    $finalTokens[$newStackPtr] = [
×
1414
                        'code'    => T_STRING,
×
1415
                        'type'    => 'T_STRING',
×
1416
                        'content' => $token[1],
×
1417
                    ];
1418
                    $newStackPtr++;
×
1419

1420
                    if (PHP_CODESNIFFER_VERBOSITY > 1 && $type !== T_STRING) {
×
1421
                        Common::printStatusMessage("* token $stackPtr changed from $type to T_STRING", 2);
×
1422
                    }
1423
                }//end if
1424

1425
                continue;
990✔
1426
            }//end if
1427

1428
            /*
1429
                Between PHP 7.0 and 7.3, the ??= operator was tokenized as
1430
                T_COALESCE, T_EQUAL.
1431
                So look for and combine these tokens in earlier versions.
1432
            */
1433

1434
            if ($tokenIsArray === true
2,898✔
1435
                && $token[0] === T_COALESCE
2,898✔
1436
                && isset($tokens[($stackPtr + 1)]) === true
2,898✔
1437
                && $tokens[($stackPtr + 1)][0] === '='
2,898✔
1438
            ) {
1439
                $newToken            = [];
×
1440
                $newToken['code']    = T_COALESCE_EQUAL;
×
1441
                $newToken['type']    = 'T_COALESCE_EQUAL';
×
1442
                $newToken['content'] = '??=';
×
1443
                $finalTokens[$newStackPtr] = $newToken;
×
1444

1445
                $newStackPtr++;
×
1446
                $stackPtr++;
×
1447

1448
                if ($tokenIsArray === false) {
×
1449
                    // Pre PHP 7.
1450
                    $stackPtr++;
×
1451
                }
1452

1453
                continue;
×
1454
            }
1455

1456
            /*
1457
                Before PHP 8, the ?-> operator was tokenized as
1458
                T_INLINE_THEN followed by T_OBJECT_OPERATOR.
1459
                So look for and combine these tokens in earlier versions.
1460
            */
1461

1462
            if ($tokenIsArray === false
2,898✔
1463
                && $token[0] === '?'
2,898✔
1464
                && isset($tokens[($stackPtr + 1)]) === true
2,898✔
1465
                && is_array($tokens[($stackPtr + 1)]) === true
2,898✔
1466
                && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR
2,898✔
1467
            ) {
1468
                $newToken            = [];
7✔
1469
                $newToken['code']    = T_NULLSAFE_OBJECT_OPERATOR;
7✔
1470
                $newToken['type']    = 'T_NULLSAFE_OBJECT_OPERATOR';
7✔
1471
                $newToken['content'] = '?->';
7✔
1472
                $finalTokens[$newStackPtr] = $newToken;
7✔
1473

1474
                $newStackPtr++;
7✔
1475
                $stackPtr++;
7✔
1476
                continue;
7✔
1477
            }
1478

1479
            /*
1480
                Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
1481
                tokens split the token with a T_STRING. So look for
1482
                and change these tokens in earlier versions.
1483
            */
1484

1485
            if (PHP_VERSION_ID < 70400
2,898✔
1486
                && ($tokenIsArray === true
2,898✔
1487
                && ($token[0] === T_LNUMBER
2,898✔
1488
                || $token[0] === T_DNUMBER)
2,898✔
1489
                && isset($tokens[($stackPtr + 1)]) === true
2,898✔
1490
                && is_array($tokens[($stackPtr + 1)]) === true
2,898✔
1491
                && $tokens[($stackPtr + 1)][0] === T_STRING
2,898✔
1492
                && $tokens[($stackPtr + 1)][1][0] === '_')
2,898✔
1493
            ) {
1494
                $newContent = $token[1];
27✔
1495
                $newType    = $token[0];
27✔
1496
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
27✔
1497
                    if (is_array($tokens[$i]) === false) {
27✔
1498
                        break;
27✔
1499
                    }
1500

1501
                    if ($tokens[$i][0] === T_LNUMBER
27✔
1502
                        || $tokens[$i][0] === T_DNUMBER
27✔
1503
                    ) {
1504
                        $newContent .= $tokens[$i][1];
27✔
1505
                        continue;
27✔
1506
                    }
1507

1508
                    if ($tokens[$i][0] === T_STRING
27✔
1509
                        && $tokens[$i][1][0] === '_'
27✔
1510
                        && ((strpos($newContent, '0x') === 0
27✔
1511
                        && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
27✔
1512
                        || (strpos($newContent, '0x') !== 0
27✔
1513
                        && substr($newContent, -1) !== '.'
27✔
1514
                        && substr(strtolower($newContent), -1) !== 'e'
27✔
1515
                        && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
27✔
1516
                    ) {
1517
                        $newContent .= $tokens[$i][1];
27✔
1518

1519
                        // Support floats.
1520
                        if (substr(strtolower($tokens[$i][1]), -1) === 'e'
27✔
1521
                            && ($tokens[($i + 1)] === '-'
27✔
1522
                            || $tokens[($i + 1)] === '+')
27✔
1523
                        ) {
1524
                            $newContent .= $tokens[($i + 1)];
27✔
1525
                            $i++;
27✔
1526
                        }
1527

1528
                        continue;
27✔
1529
                    }//end if
1530

1531
                    break;
27✔
1532
                }//end for
1533

1534
                if ($newType === T_LNUMBER
27✔
1535
                    && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1536
                    || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1537
                    || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1538
                    || (stripos($newContent, '0x') !== 0
27✔
1539
                    && (stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false))
27✔
1540
                    || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
27✔
1541
                    && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
27✔
1542
                    || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
27✔
1543
                ) {
1544
                    $newType = T_DNUMBER;
27✔
1545
                }
1546

1547
                $newToken            = [];
27✔
1548
                $newToken['code']    = $newType;
27✔
1549
                $newToken['type']    = Tokens::tokenName($newType);
27✔
1550
                $newToken['content'] = $newContent;
27✔
1551
                $finalTokens[$newStackPtr] = $newToken;
27✔
1552

1553
                $newStackPtr++;
27✔
1554
                $stackPtr = ($i - 1);
27✔
1555
                continue;
27✔
1556
            }//end if
1557

1558
            /*
1559
                Before PHP 8.0, namespaced names were not tokenized as a single token.
1560

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

1567
            if (PHP_VERSION_ID < 80000
2,898✔
1568
                && $tokenIsArray === true
2,898✔
1569
                && ($token[0] === T_STRING
2,898✔
1570
                || $token[0] === T_NAMESPACE
2,898✔
1571
                || ($token[0] === T_NS_SEPARATOR
2,898✔
1572
                && isset($tokens[($stackPtr + 1)]) === true
2,898✔
1573
                && is_array($tokens[($stackPtr + 1)]) === true
2,898✔
1574
                && isset(Tokens::$emptyTokens[$tokens[($stackPtr + 1)][0]]) === false
2,898✔
1575
                && preg_match(self::PHP_LABEL_REGEX, $tokens[($stackPtr + 1)][1]) === 1))
2,898✔
1576
            ) {
1577
                $nameStart = $stackPtr;
863✔
1578
                $i         = $stackPtr;
863✔
1579
                $newToken  = [];
863✔
1580
                $newToken['content'] = $token[1];
863✔
1581

1582
                switch ($token[0]) {
863✔
1583
                case T_STRING:
863✔
1584
                    $newToken['code'] = T_NAME_QUALIFIED;
863✔
1585
                    $newToken['type'] = 'T_NAME_QUALIFIED';
863✔
1586
                    break;
863✔
1587
                case T_NAMESPACE:
590✔
1588
                    $newToken['code'] = T_NAME_RELATIVE;
573✔
1589
                    $newToken['type'] = 'T_NAME_RELATIVE';
573✔
1590
                    break;
573✔
1591
                case T_NS_SEPARATOR:
340✔
1592
                    $newToken['code'] = T_NAME_FULLY_QUALIFIED;
340✔
1593
                    $newToken['type'] = 'T_NAME_FULLY_QUALIFIED';
340✔
1594

1595
                    if (is_array($tokens[($i - 1)]) === true
340✔
1596
                        && isset(Tokens::$emptyTokens[$tokens[($i - 1)][0]]) === false
340✔
1597
                        && preg_match(self::PHP_LABEL_REGEX, $tokens[($i - 1)][1]) === 1
340✔
1598
                    ) {
1599
                        // The namespaced name starts with a reserved keyword. Move one token back.
1600
                        $newToken['code']    = T_NAME_QUALIFIED;
47✔
1601
                        $newToken['type']    = 'T_NAME_QUALIFIED';
47✔
1602
                        $newToken['content'] = $tokens[($i - 1)][1];
47✔
1603
                        --$nameStart;
47✔
1604
                        --$i;
47✔
1605
                        break;
47✔
1606
                    }
1607

1608
                    ++$i;
340✔
1609
                    $newToken['content'] .= $tokens[$i][1];
340✔
1610
                    break;
340✔
1611
                }//end switch
1612

1613
                while (isset($tokens[($i + 1)], $tokens[($i + 2)]) === true
863✔
1614
                    && is_array($tokens[($i + 1)]) === true && $tokens[($i + 1)][0] === T_NS_SEPARATOR
863✔
1615
                    && is_array($tokens[($i + 2)]) === true
863✔
1616
                    && isset(Tokens::$emptyTokens[$tokens[($i + 2)][0]]) === false
863✔
1617
                    && preg_match(self::PHP_LABEL_REGEX, $tokens[($i + 2)][1]) === 1
863✔
1618
                ) {
1619
                    $newToken['content'] .= $tokens[($i + 1)][1].$tokens[($i + 2)][1];
543✔
1620
                    $i = ($i + 2);
543✔
1621
                }
1622

1623
                if ($i !== $nameStart) {
863✔
1624
                    if ($nameStart !== $stackPtr) {
543✔
1625
                        // This must be a qualified name starting with a reserved keyword.
1626
                        // We need to overwrite the previously set final token.
1627
                        --$newStackPtr;
47✔
1628
                    }
1629

1630
                    $finalTokens[$newStackPtr] = $newToken;
543✔
1631
                    $newStackPtr++;
543✔
1632
                    $stackPtr = $i;
543✔
1633

1634
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
543✔
1635
                        $type    = $newToken['type'];
×
1636
                        $content = $newToken['content'];
×
1637
                        Common::printStatusMessage("* token $nameStart to $i ($content) retokenized to $type", 2);
×
1638
                    }
1639

1640
                    continue;
543✔
1641
                }
1642
            }//end if
1643

1644
            /*
1645
                Backfill the T_MATCH token for PHP versions < 8.0 and
1646
                do initial correction for non-match expression T_MATCH tokens
1647
                to T_STRING for PHP >= 8.0.
1648
                A final check for non-match expression T_MATCH tokens is done
1649
                in PHP::processAdditional().
1650
            */
1651

1652
            if ($tokenIsArray === true
2,898✔
1653
                && (($token[0] === T_STRING
2,898✔
1654
                && strtolower($token[1]) === 'match')
2,795✔
1655
                || $token[0] === T_MATCH)
2,898✔
1656
            ) {
1657
                $isMatch = false;
720✔
1658
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
720✔
1659
                    if (isset($tokens[$x][0], Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
720✔
1660
                        continue;
720✔
1661
                    }
1662

1663
                    if ($tokens[$x] !== '(') {
720✔
1664
                        // This is not a match expression.
1665
                        break;
344✔
1666
                    }
1667

1668
                    if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
720✔
1669
                        // Also not a match expression.
1670
                        break;
174✔
1671
                    }
1672

1673
                    $isMatch = true;
720✔
1674
                    break;
720✔
1675
                }//end for
1676

1677
                if ($isMatch === true && $token[0] === T_STRING) {
720✔
1678
                    $newToken            = [];
240✔
1679
                    $newToken['code']    = T_MATCH;
240✔
1680
                    $newToken['type']    = 'T_MATCH';
240✔
1681
                    $newToken['content'] = $token[1];
240✔
1682

1683
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
240✔
1684
                        Common::printStatusMessage("* token $stackPtr changed from T_STRING to T_MATCH", 2);
×
1685
                    }
1686

1687
                    $finalTokens[$newStackPtr] = $newToken;
240✔
1688
                    $newStackPtr++;
240✔
1689
                    continue;
240✔
1690
                } else if ($isMatch === false && $token[0] === T_MATCH) {
708✔
1691
                    // PHP 8.0, match keyword, but not a match expression.
1692
                    $newToken            = [];
116✔
1693
                    $newToken['code']    = T_STRING;
116✔
1694
                    $newToken['type']    = 'T_STRING';
116✔
1695
                    $newToken['content'] = $token[1];
116✔
1696

1697
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
116✔
1698
                        Common::printStatusMessage("* token $stackPtr changed from T_MATCH to T_STRING", 2);
×
1699
                    }
1700

1701
                    $finalTokens[$newStackPtr] = $newToken;
116✔
1702
                    $newStackPtr++;
116✔
1703
                    continue;
116✔
1704
                }//end if
1705
            }//end if
1706

1707
            /*
1708
                Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
1709
                to prevent scope being set and the scope for switch default statements
1710
                breaking.
1711
            */
1712

1713
            if ($tokenIsArray === true
2,898✔
1714
                && $token[0] === T_DEFAULT
2,898✔
1715
                && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
2,898✔
1716
            ) {
1717
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1,359✔
1718
                    if ($tokens[$x] === ',') {
1,359✔
1719
                        // Skip over potential trailing comma (supported in PHP).
1720
                        continue;
210✔
1721
                    }
1722

1723
                    if (is_array($tokens[$x]) === false
1,359✔
1724
                        || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
1,359✔
1725
                    ) {
1726
                        // Non-empty, non-comma content.
1727
                        break;
1,359✔
1728
                    }
1729
                }
1730

1731
                if (isset($tokens[$x]) === true
1,359✔
1732
                    && is_array($tokens[$x]) === true
1,359✔
1733
                    && $tokens[$x][0] === T_DOUBLE_ARROW
1,359✔
1734
                ) {
1735
                    // Modify the original token stack for the double arrow so that
1736
                    // future checks can disregard the double arrow token more easily.
1737
                    // For match expression "case" statements, this is handled
1738
                    // in PHP::processAdditional().
1739
                    $tokens[$x][0] = T_MATCH_ARROW;
720✔
1740
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
720✔
1741
                        Common::printStatusMessage("* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT", 2);
×
1742
                    }
1743

1744
                    $newToken            = [];
720✔
1745
                    $newToken['code']    = T_MATCH_DEFAULT;
720✔
1746
                    $newToken['type']    = 'T_MATCH_DEFAULT';
720✔
1747
                    $newToken['content'] = $token[1];
720✔
1748

1749
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
720✔
1750
                        Common::printStatusMessage("* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT", 2);
×
1751
                    }
1752

1753
                    $finalTokens[$newStackPtr] = $newToken;
720✔
1754
                    $newStackPtr++;
720✔
1755
                    continue;
720✔
1756
                }//end if
1757
            }//end if
1758

1759
            /*
1760
                Convert ? to T_NULLABLE OR T_INLINE_THEN
1761
            */
1762

1763
            if ($tokenIsArray === false && $token[0] === '?') {
2,898✔
1764
                $newToken            = [];
1,197✔
1765
                $newToken['content'] = '?';
1,197✔
1766

1767
                // For typed constants, we only need to check the token before the ? to be sure.
1768
                if ($finalTokens[$lastNotEmptyToken]['code'] === T_CONST) {
1,197✔
1769
                    $newToken['code'] = T_NULLABLE;
189✔
1770
                    $newToken['type'] = 'T_NULLABLE';
189✔
1771

1772
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
189✔
1773
                        echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
×
1774
                    }
1775

1776
                    $finalTokens[$newStackPtr] = $newToken;
189✔
1777
                    $newStackPtr++;
189✔
1778
                    continue;
189✔
1779
                }
1780

1781
                /*
1782
                 * Check if the next non-empty token is one of the tokens which can be used
1783
                 * in type declarations. If not, it's definitely a ternary.
1784
                 * At this point, the only token types which need to be taken into consideration
1785
                 * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
1786
                 */
1787

1788
                $lastRelevantNonEmpty = null;
1,197✔
1789

1790
                for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,197✔
1791
                    if (is_array($tokens[$i]) === true) {
1,197✔
1792
                        $tokenType = $tokens[$i][0];
1,197✔
1793
                    } else {
1794
                        $tokenType = $tokens[$i];
871✔
1795
                    }
1796

1797
                    if (isset(Tokens::$emptyTokens[$tokenType]) === true) {
1,197✔
1798
                        continue;
1,197✔
1799
                    }
1800

1801
                    if ($tokenType === T_STRING
1,197✔
1802
                        || $tokenType === T_NAME_FULLY_QUALIFIED
1,197✔
1803
                        || $tokenType === T_NAME_RELATIVE
1,197✔
1804
                        || $tokenType === T_NAME_QUALIFIED
1,197✔
1805
                        || $tokenType === T_ARRAY
1,197✔
1806
                        || $tokenType === T_NAMESPACE
1,197✔
1807
                        || $tokenType === T_NS_SEPARATOR
1,197✔
1808
                    ) {
1809
                        $lastRelevantNonEmpty = $tokenType;
871✔
1810
                        continue;
871✔
1811
                    }
1812

1813
                    if (($tokenType !== T_CALLABLE
1,197✔
1814
                        && isset($lastRelevantNonEmpty) === false)
1,197✔
1815
                        || ($lastRelevantNonEmpty === T_ARRAY
871✔
1816
                        && $tokenType === '(')
871✔
1817
                        || (($lastRelevantNonEmpty === T_STRING
1,127✔
1818
                        || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED
798✔
1819
                        || $lastRelevantNonEmpty === T_NAME_RELATIVE
798✔
1820
                        || $lastRelevantNonEmpty === T_NAME_QUALIFIED)
1,127✔
1821
                        && ($tokenType === T_DOUBLE_COLON
1,127✔
1822
                        || $tokenType === '('
1,127✔
1823
                        || $tokenType === ':'))
1,197✔
1824
                    ) {
1825
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,056✔
1826
                            Common::printStatusMessage("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
1827
                        }
1828

1829
                        $newToken['code'] = T_INLINE_THEN;
1,056✔
1830
                        $newToken['type'] = 'T_INLINE_THEN';
1,056✔
1831

1832
                        $insideInlineIf[] = $stackPtr;
1,056✔
1833

1834
                        $finalTokens[$newStackPtr] = $newToken;
1,056✔
1835
                        $newStackPtr++;
1,056✔
1836
                        continue 2;
1,056✔
1837
                    }
1838

1839
                    break;
141✔
1840
                }//end for
1841

1842
                /*
1843
                 * This can still be a nullable type or a ternary.
1844
                 * Do additional checking.
1845
                 */
1846

1847
                $prevNonEmpty     = null;
162✔
1848
                $lastSeenNonEmpty = null;
162✔
1849

1850
                for ($i = ($stackPtr - 1); $i >= 0; $i--) {
162✔
1851
                    if (is_array($tokens[$i]) === true) {
162✔
1852
                        $tokenType = $tokens[$i][0];
162✔
1853
                    } else {
1854
                        $tokenType = $tokens[$i];
162✔
1855
                    }
1856

1857
                    if ($tokenType === T_STATIC
162✔
1858
                        && ($lastSeenNonEmpty === T_DOUBLE_COLON
108✔
1859
                        || $lastSeenNonEmpty === '(')
162✔
1860
                    ) {
1861
                        $lastSeenNonEmpty = $tokenType;
×
1862
                        continue;
×
1863
                    }
1864

1865
                    if ($prevNonEmpty === null
162✔
1866
                        && isset(Tokens::$emptyTokens[$tokenType]) === false
162✔
1867
                    ) {
1868
                        // Found the previous non-empty token.
1869
                        if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) {
162✔
1870
                            $newToken['code'] = T_NULLABLE;
141✔
1871
                            $newToken['type'] = 'T_NULLABLE';
141✔
1872

1873
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
141✔
1874
                                Common::printStatusMessage("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
1875
                            }
1876

1877
                            break;
141✔
1878
                        }
1879

1880
                        $prevNonEmpty = $tokenType;
162✔
1881
                    }
1882

1883
                    if ($tokenType === T_FUNCTION
162✔
1884
                        || $tokenType === T_FN
162✔
1885
                        || isset(Tokens::$methodPrefixes[$tokenType]) === true
162✔
1886
                        || $tokenType === T_VAR
162✔
1887
                    ) {
1888
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
141✔
1889
                            Common::printStatusMessage("* token $stackPtr changed from ? to T_NULLABLE", 2);
×
1890
                        }
1891

1892
                        $newToken['code'] = T_NULLABLE;
141✔
1893
                        $newToken['type'] = 'T_NULLABLE';
141✔
1894
                        break;
141✔
1895
                    } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
162✔
1896
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
21✔
1897
                            Common::printStatusMessage("* token $stackPtr changed from ? to T_INLINE_THEN", 2);
×
1898
                        }
1899

1900
                        $newToken['code'] = T_INLINE_THEN;
21✔
1901
                        $newToken['type'] = 'T_INLINE_THEN';
21✔
1902

1903
                        $insideInlineIf[] = $stackPtr;
21✔
1904
                        break;
21✔
1905
                    }
1906

1907
                    if (isset(Tokens::$emptyTokens[$tokenType]) === false) {
162✔
1908
                        $lastSeenNonEmpty = $tokenType;
162✔
1909
                    }
1910
                }//end for
1911

1912
                $finalTokens[$newStackPtr] = $newToken;
162✔
1913
                $newStackPtr++;
162✔
1914
                continue;
162✔
1915
            }//end if
1916

1917
            /*
1918
                Tokens after a double colon may look like scope openers,
1919
                such as when writing code like Foo::NAMESPACE, but they are
1920
                only ever variables or strings.
1921
            */
1922

1923
            if ($stackPtr > 1
2,898✔
1924
                && (is_array($tokens[($stackPtr - 1)]) === true
2,898✔
1925
                && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
2,898✔
1926
                && $tokenIsArray === true
2,898✔
1927
                && $token[0] !== T_STRING
2,898✔
1928
                && $token[0] !== T_VARIABLE
2,898✔
1929
                && $token[0] !== T_DOLLAR
2,898✔
1930
                && isset(Tokens::$emptyTokens[$token[0]]) === false
2,898✔
1931
            ) {
1932
                $newToken            = [];
×
1933
                $newToken['code']    = T_STRING;
×
1934
                $newToken['type']    = 'T_STRING';
×
1935
                $newToken['content'] = $token[1];
×
1936
                $finalTokens[$newStackPtr] = $newToken;
×
1937

1938
                $newStackPtr++;
×
1939
                continue;
×
1940
            }
1941

1942
            /*
1943
                Backfill the T_FN token for PHP versions < 7.4.
1944
            */
1945

1946
            if ($tokenIsArray === true
2,898✔
1947
                && $token[0] === T_STRING
2,898✔
1948
                && strtolower($token[1]) === 'fn'
2,898✔
1949
            ) {
1950
                // Modify the original token stack so that
1951
                // future checks (like looking for T_NULLABLE) can
1952
                // detect the T_FN token more easily.
1953
                $tokens[$stackPtr][0] = T_FN;
637✔
1954
                $token[0] = T_FN;
637✔
1955
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
637✔
1956
                    Common::printStatusMessage("* token $stackPtr changed from T_STRING to T_FN", 2);
×
1957
                }
1958
            }
1959

1960
            /*
1961
                This is a special condition for T_ARRAY tokens used for
1962
                function return types. We want to keep the parenthesis map clean,
1963
                so let's tag these tokens as T_STRING.
1964
            */
1965

1966
            if ($tokenIsArray === true
2,898✔
1967
                && ($token[0] === T_FUNCTION
2,898✔
1968
                || $token[0] === T_FN)
2,898✔
1969
                && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
2,898✔
1970
            ) {
1971
                // Go looking for the colon to start the return type hint.
1972
                // Start by finding the closing parenthesis of the function.
1973
                $parenthesisStack  = [];
2,235✔
1974
                $parenthesisCloser = false;
2,235✔
1975
                for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
2,235✔
1976
                    if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
2,235✔
1977
                        $parenthesisStack[] = $x;
2,235✔
1978
                    } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
2,235✔
1979
                        array_pop($parenthesisStack);
2,235✔
1980
                        if (empty($parenthesisStack) === true) {
2,235✔
1981
                            $parenthesisCloser = $x;
2,235✔
1982
                            break;
2,235✔
1983
                        }
1984
                    }
1985
                }
1986

1987
                if ($parenthesisCloser !== false) {
2,235✔
1988
                    for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
2,235✔
1989
                        if (is_array($tokens[$x]) === false
2,235✔
1990
                            || isset(Tokens::$emptyTokens[$tokens[$x][0]]) === false
2,235✔
1991
                        ) {
1992
                            // Non-empty content.
1993
                            if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
2,235✔
1994
                                // Found a use statements, so search ahead for the closing parenthesis.
1995
                                for ($x += 1; $x < $numTokens; $x++) {
57✔
1996
                                    if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
57✔
1997
                                        continue(2);
57✔
1998
                                    }
1999
                                }
2000
                            }
2001

2002
                            break;
2,235✔
2003
                        }
2004
                    }
2005

2006
                    if (isset($tokens[$x]) === true
2,235✔
2007
                        && is_array($tokens[$x]) === false
2,235✔
2008
                        && $tokens[$x] === ':'
2,235✔
2009
                    ) {
2010
                        // Find the start of the return type.
2011
                        for ($x += 1; $x < $numTokens; $x++) {
1,956✔
2012
                            if (is_array($tokens[$x]) === true
1,956✔
2013
                                && isset(Tokens::$emptyTokens[$tokens[$x][0]]) === true
1,956✔
2014
                            ) {
2015
                                // Whitespace or comments before the return type.
2016
                                continue;
1,956✔
2017
                            }
2018

2019
                            if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1,956✔
2020
                                // Found a nullable operator, so skip it.
2021
                                // But also convert the token to save the tokenizer
2022
                                // a bit of time later on.
2023
                                $tokens[$x] = [
141✔
2024
                                    T_NULLABLE,
141✔
2025
                                    '?',
141✔
2026
                                ];
94✔
2027

2028
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
141✔
2029
                                    Common::printStatusMessage("* token $x changed from ? to T_NULLABLE", 2);
×
2030
                                }
2031

2032
                                continue;
141✔
2033
                            }
2034

2035
                            break;
1,956✔
2036
                        }//end for
2037
                    }//end if
2038
                }//end if
2039
            }//end if
2040

2041
            /*
2042
                PHP doesn't assign a token to goto labels, so we have to.
2043
                These are just string tokens with a single colon after them. Double
2044
                colons are already tokenized and so don't interfere with this check.
2045
                But we do have to account for CASE statements, that look just like
2046
                goto labels.
2047
            */
2048

2049
            if ($tokenIsArray === true
2,898✔
2050
                && $token[0] === T_STRING
2,898✔
2051
                && isset($tokens[($stackPtr + 1)]) === true
2,898✔
2052
                && $tokens[($stackPtr + 1)] === ':'
2,898✔
2053
                && (is_array($tokens[($stackPtr - 1)]) === false
2,381✔
2054
                || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM)
2,898✔
2055
            ) {
2056
                $stopTokens = [
898✔
2057
                    T_CASE               => true,
1,347✔
2058
                    T_SEMICOLON          => true,
1,347✔
2059
                    T_OPEN_TAG           => true,
1,347✔
2060
                    T_OPEN_CURLY_BRACKET => true,
1,347✔
2061
                    T_INLINE_THEN        => true,
1,347✔
2062
                    T_ENUM               => true,
1,347✔
2063
                ];
898✔
2064

2065
                for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1,347✔
2066
                    if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1,347✔
2067
                        break;
1,347✔
2068
                    }
2069
                }
2070

2071
                if ($finalTokens[$x]['code'] !== T_CASE
1,347✔
2072
                    && $finalTokens[$x]['code'] !== T_INLINE_THEN
1,347✔
2073
                    && $finalTokens[$x]['code'] !== T_ENUM
1,347✔
2074
                ) {
2075
                    $finalTokens[$newStackPtr] = [
96✔
2076
                        'content' => $token[1].':',
96✔
2077
                        'code'    => T_GOTO_LABEL,
96✔
2078
                        'type'    => 'T_GOTO_LABEL',
96✔
2079
                    ];
64✔
2080

2081
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
96✔
2082
                        Common::printStatusMessage("* token $stackPtr changed from T_STRING to T_GOTO_LABEL", 2);
×
2083
                        Common::printStatusMessage('* skipping T_COLON token '.($stackPtr + 1), 2);
×
2084
                    }
2085

2086
                    $newStackPtr++;
96✔
2087
                    $stackPtr++;
96✔
2088
                    continue;
96✔
2089
                }
2090
            }//end if
2091

2092
            /*
2093
                If this token has newlines in its content, split each line up
2094
                and create a new token for each line. We do this so it's easier
2095
                to ascertain where errors occur on a line.
2096
                Note that $token[1] is the token's content.
2097
            */
2098

2099
            if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
2,898✔
2100
                $tokenLines = explode($this->eolChar, $token[1]);
2,898✔
2101
                $numLines   = count($tokenLines);
2,898✔
2102
                $newToken   = [
1,932✔
2103
                    'type'    => Tokens::tokenName($token[0]),
2,898✔
2104
                    'code'    => $token[0],
2,898✔
2105
                    'content' => '',
2,898✔
2106
                ];
1,932✔
2107

2108
                for ($i = 0; $i < $numLines; $i++) {
2,898✔
2109
                    $newToken['content'] = $tokenLines[$i];
2,898✔
2110
                    if ($i === ($numLines - 1)) {
2,898✔
2111
                        if ($tokenLines[$i] === '') {
2,898✔
2112
                            break;
2,898✔
2113
                        }
2114
                    } else {
2115
                        $newToken['content'] .= $this->eolChar;
2,898✔
2116
                    }
2117

2118
                    $finalTokens[$newStackPtr] = $newToken;
2,898✔
2119
                    $newStackPtr++;
2,898✔
2120
                }
2121
            } else {
2122
                // Some T_STRING tokens should remain that way due to their context.
2123
                if ($tokenIsArray === true && $token[0] === T_STRING) {
2,898✔
2124
                    $preserveTstring = false;
2,589✔
2125

2126
                    // True/false/parent/self/static in typed constants should be fixed to their own token,
2127
                    // but the constant name should not be.
2128
                    if ((isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,589✔
2129
                        && $finalTokens[$lastNotEmptyToken]['code'] === T_CONST)
2,478✔
2130
                        || $insideConstDeclaration === true
2,589✔
2131
                    ) {
2132
                        // Find the next non-empty token.
2133
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,564✔
2134
                            if (is_array($tokens[$i]) === true
1,564✔
2135
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
1,564✔
2136
                            ) {
2137
                                continue;
1,423✔
2138
                            }
2139

2140
                            break;
1,564✔
2141
                        }
2142

2143
                        if ($tokens[$i] === '=') {
1,564✔
2144
                            $preserveTstring        = true;
1,423✔
2145
                            $insideConstDeclaration = false;
1,517✔
2146
                        }
2147
                    } else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
2,589✔
2148
                        && $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
2,589✔
2149
                    ) {
2150
                        $preserveTstring = true;
2,478✔
2151

2152
                        // Special case for syntax like: return new self/new parent
2153
                        // where self/parent should not be a string.
2154
                        $tokenContentLower = strtolower($token[1]);
2,478✔
2155
                        if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
2,478✔
2156
                            && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
2,478✔
2157
                        ) {
2158
                            $preserveTstring = false;
2,025✔
2159
                        }
2160
                    } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
2,568✔
2161
                        // Function names for functions declared to return by reference.
2162
                        for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
1,047✔
2163
                            if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
1,047✔
2164
                                continue;
480✔
2165
                            }
2166

2167
                            if ($finalTokens[$i]['code'] === T_FUNCTION) {
1,047✔
2168
                                $preserveTstring = true;
480✔
2169
                            }
2170

2171
                            break;
1,047✔
2172
                        }
2173
                    } else {
2174
                        // Keywords with special PHPCS token when used as a function call.
2175
                        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
2,568✔
2176
                            if (is_array($tokens[$i]) === true
2,568✔
2177
                                && isset(Tokens::$emptyTokens[$tokens[$i][0]]) === true
2,568✔
2178
                            ) {
2179
                                continue;
2,232✔
2180
                            }
2181

2182
                            if ($tokens[$i][0] === '(') {
2,568✔
2183
                                $preserveTstring = true;
2,097✔
2184
                            }
2185

2186
                            break;
2,568✔
2187
                        }
2188
                    }//end if
2189

2190
                    if ($preserveTstring === true) {
2,589✔
2191
                        $finalTokens[$newStackPtr] = [
2,478✔
2192
                            'code'    => T_STRING,
2,478✔
2193
                            'type'    => 'T_STRING',
2,478✔
2194
                            'content' => $token[1],
2,478✔
2195
                        ];
1,652✔
2196

2197
                        $newStackPtr++;
2,478✔
2198
                        continue;
2,478✔
2199
                    }
2200
                }//end if
2201

2202
                $newToken = null;
2,898✔
2203
                if ($tokenIsArray === false) {
2,898✔
2204
                    if (isset(self::$resolveTokenCache[$token[0]]) === true) {
2,898✔
2205
                        $newToken = self::$resolveTokenCache[$token[0]];
2,898✔
2206
                    }
2207
                } else {
2208
                    $cacheKey = null;
2,898✔
2209
                    if ($token[0] === T_STRING) {
2,898✔
2210
                        $cacheKey = strtolower($token[1]);
2,568✔
2211
                    } else if ($token[0] !== T_CURLY_OPEN) {
2,898✔
2212
                        $cacheKey = $token[0];
2,898✔
2213
                    }
2214

2215
                    if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
2,898✔
2216
                        $newToken            = self::$resolveTokenCache[$cacheKey];
2,898✔
2217
                        $newToken['content'] = $token[1];
2,898✔
2218
                    }
2219
                }
2220

2221
                if ($newToken === null) {
2,898✔
2222
                    $newToken = self::standardiseToken($token);
42✔
2223
                }
2224

2225
                // Convert colons that are actually the ELSE component of an
2226
                // inline IF statement.
2227
                if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
2,898✔
2228
                    $isInlineIf = true;
1,056✔
2229

2230
                    // Make sure this isn't a named parameter label.
2231
                    // Get the previous non-empty token.
2232
                    for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,056✔
2233
                        if (is_array($tokens[$i]) === false
1,056✔
2234
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,056✔
2235
                        ) {
2236
                            break;
1,056✔
2237
                        }
2238
                    }
2239

2240
                    if ($tokens[$i][0] === T_PARAM_NAME) {
1,056✔
2241
                        $isInlineIf = false;
639✔
2242
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2243
                            Common::printStatusMessage('* token is parameter label, not T_INLINE_ELSE', 2);
×
2244
                        }
2245
                    }
2246

2247
                    if ($isInlineIf === true) {
1,056✔
2248
                        // Make sure this isn't a return type separator.
2249
                        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1,056✔
2250
                            if (is_array($tokens[$i]) === false
1,056✔
2251
                                || ($tokens[$i][0] !== T_DOC_COMMENT
1,056✔
2252
                                && $tokens[$i][0] !== T_COMMENT
1,056✔
2253
                                && $tokens[$i][0] !== T_WHITESPACE)
1,056✔
2254
                            ) {
2255
                                break;
1,056✔
2256
                            }
2257
                        }
2258

2259
                        if ($tokens[$i] === ')') {
1,056✔
2260
                            $parenCount = 1;
639✔
2261
                            for ($i--; $i > 0; $i--) {
639✔
2262
                                if ($tokens[$i] === '(') {
639✔
2263
                                    $parenCount--;
639✔
2264
                                    if ($parenCount === 0) {
639✔
2265
                                        break;
639✔
2266
                                    }
2267
                                } else if ($tokens[$i] === ')') {
639✔
2268
                                    $parenCount++;
×
2269
                                }
2270
                            }
2271

2272
                            // We've found the open parenthesis, so if the previous
2273
                            // non-empty token is FUNCTION or USE, this is a return type.
2274
                            // Note that we need to skip T_STRING tokens here as these
2275
                            // can be function names.
2276
                            for ($i--; $i > 0; $i--) {
639✔
2277
                                if (is_array($tokens[$i]) === false
639✔
2278
                                    || ($tokens[$i][0] !== T_DOC_COMMENT
639✔
2279
                                    && $tokens[$i][0] !== T_COMMENT
639✔
2280
                                    && $tokens[$i][0] !== T_WHITESPACE
639✔
2281
                                    && $tokens[$i][0] !== T_STRING)
639✔
2282
                                ) {
2283
                                    break;
639✔
2284
                                }
2285
                            }
2286

2287
                            if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
639✔
2288
                                $isInlineIf = false;
639✔
2289
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
639✔
2290
                                    Common::printStatusMessage('* token is return type, not T_INLINE_ELSE', 2);
×
2291
                                }
2292
                            }
2293
                        }//end if
2294
                    }//end if
2295

2296
                    // Check to see if this is a CASE or DEFAULT opener.
2297
                    if ($isInlineIf === true) {
1,056✔
2298
                        $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1,056✔
2299
                        for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1,056✔
2300
                            if (is_array($tokens[$i]) === true
1,056✔
2301
                                && ($tokens[$i][0] === T_CASE
1,056✔
2302
                                || $tokens[$i][0] === T_DEFAULT)
1,056✔
2303
                            ) {
2304
                                $isInlineIf = false;
×
2305
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
2306
                                    Common::printStatusMessage('* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE', 2);
×
2307
                                }
2308

2309
                                break;
×
2310
                            }
2311

2312
                            if (is_array($tokens[$i]) === false
1,056✔
2313
                                && ($tokens[$i] === ';'
1,056✔
2314
                                || $tokens[$i] === '{'
1,056✔
2315
                                || $tokens[$i] === '}')
1,056✔
2316
                            ) {
2317
                                break;
813✔
2318
                            }
2319
                        }//end for
2320
                    }//end if
2321

2322
                    if ($isInlineIf === true) {
1,056✔
2323
                        array_pop($insideInlineIf);
1,056✔
2324
                        $newToken['code'] = T_INLINE_ELSE;
1,056✔
2325
                        $newToken['type'] = 'T_INLINE_ELSE';
1,056✔
2326

2327
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,056✔
2328
                            Common::printStatusMessage('* token changed from T_COLON to T_INLINE_ELSE', 2);
×
2329
                        }
2330
                    }
2331
                }//end if
2332

2333
                // This is a special condition for T_ARRAY tokens used for anything else
2334
                // but array declarations, like type hinting function arguments as
2335
                // being arrays.
2336
                // We want to keep the parenthesis map clean, so let's tag these tokens as
2337
                // T_STRING.
2338
                if ($newToken['code'] === T_ARRAY) {
2,898✔
2339
                    for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1,497✔
2340
                        if (is_array($tokens[$i]) === false
1,497✔
2341
                            || isset(Tokens::$emptyTokens[$tokens[$i][0]]) === false
1,497✔
2342
                        ) {
2343
                            // Non-empty content.
2344
                            break;
1,497✔
2345
                        }
2346
                    }
2347

2348
                    if ($i !== $numTokens && $tokens[$i] !== '(') {
1,497✔
2349
                        $newToken['code'] = T_STRING;
1,287✔
2350
                        $newToken['type'] = 'T_STRING';
1,287✔
2351
                    }
2352
                }
2353

2354
                // This is a special case when checking PHP 5.5+ code in PHP < 5.5
2355
                // where "finally" should be T_FINALLY instead of T_STRING.
2356
                if ($newToken['code'] === T_STRING
2,898✔
2357
                    && strtolower($newToken['content']) === 'finally'
2,898✔
2358
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_CLOSE_CURLY_BRACKET
2,898✔
2359
                ) {
2360
                    $newToken['code'] = T_FINALLY;
×
2361
                    $newToken['type'] = 'T_FINALLY';
×
2362
                }
2363

2364
                // This is a special case for PHP 5.6 use function and use const
2365
                // where "function" and "const" should be T_STRING instead of T_FUNCTION
2366
                // and T_CONST.
2367
                if (($newToken['code'] === T_FUNCTION
2,898✔
2368
                    || $newToken['code'] === T_CONST)
2,898✔
2369
                    && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
2,898✔
2370
                ) {
2371
                    $newToken['code'] = T_STRING;
141✔
2372
                    $newToken['type'] = 'T_STRING';
141✔
2373
                }
2374

2375
                // This is a special case for use groups in PHP 7+ where leaving
2376
                // the curly braces as their normal tokens would confuse
2377
                // the scope map and sniffs.
2378
                if ($newToken['code'] === T_OPEN_CURLY_BRACKET
2,898✔
2379
                    && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
2,898✔
2380
                ) {
2381
                    $newToken['code'] = T_OPEN_USE_GROUP;
141✔
2382
                    $newToken['type'] = 'T_OPEN_USE_GROUP';
141✔
2383
                    $insideUseGroup   = true;
141✔
2384
                }
2385

2386
                if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
2,898✔
2387
                    $newToken['code'] = T_CLOSE_USE_GROUP;
141✔
2388
                    $newToken['type'] = 'T_CLOSE_USE_GROUP';
141✔
2389
                    $insideUseGroup   = false;
141✔
2390
                }
2391

2392
                $finalTokens[$newStackPtr] = $newToken;
2,898✔
2393
                $newStackPtr++;
2,898✔
2394
            }//end if
2395
        }//end for
2396

2397
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
2,898✔
2398
            Common::printStatusMessage('*** END PHP TOKENIZING ***', 1);
×
2399
        }
2400

2401
        return $finalTokens;
2,898✔
2402

2403
    }//end tokenize()
2404

2405

2406
    /**
2407
     * Performs additional processing after main tokenizing.
2408
     *
2409
     * This additional processing checks for CASE statements that are using curly
2410
     * braces for scope openers and closers. It also turns some T_FUNCTION tokens
2411
     * into T_CLOSURE when they are not standard function definitions. It also
2412
     * detects short array syntax and converts those square brackets into new tokens.
2413
     * It also corrects some usage of the static and class keywords. It also
2414
     * assigns tokens to function return types.
2415
     *
2416
     * @return void
2417
     */
2418
    protected function processAdditional()
1,443✔
2419
    {
2420
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,443✔
2421
            Common::printStatusMessage('*** START ADDITIONAL PHP PROCESSING ***', 1);
×
2422
        }
2423

2424
        $this->createAttributesNestingMap();
1,443✔
2425

2426
        $numTokens = count($this->tokens);
1,443✔
2427
        for ($i = ($numTokens - 1); $i >= 0; $i--) {
1,443✔
2428
            // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
2429
            if (isset($this->tokens[$i]['scope_opener']) === true
1,443✔
2430
                && isset($this->tokens[$i]['scope_condition']) === false
1,443✔
2431
            ) {
2432
                $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
×
2433
            }
2434

2435
            if ($this->tokens[$i]['code'] === T_FUNCTION) {
1,443✔
2436
                /*
2437
                    Detect functions that are actually closures and
2438
                    assign them a different token.
2439
                */
2440

2441
                if (isset($this->tokens[$i]['scope_opener']) === true) {
1,167✔
2442
                    for ($x = ($i + 1); $x < $numTokens; $x++) {
1,167✔
2443
                        if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1,167✔
2444
                            && $this->tokens[$x]['code'] !== T_BITWISE_AND
1,167✔
2445
                        ) {
2446
                            break;
1,167✔
2447
                        }
2448
                    }
2449

2450
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,167✔
2451
                        $this->tokens[$i]['code'] = T_CLOSURE;
843✔
2452
                        $this->tokens[$i]['type'] = 'T_CLOSURE';
843✔
2453
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
843✔
2454
                            $line = $this->tokens[$i]['line'];
×
2455
                            Common::printStatusMessage("* token $i on line $line changed from T_FUNCTION to T_CLOSURE", 1);
×
2456
                        }
2457

2458
                        for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
843✔
2459
                            if (isset($this->tokens[$x]['conditions'][$i]) === false) {
321✔
2460
                                continue;
×
2461
                            }
2462

2463
                            $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
321✔
2464
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
321✔
2465
                                $type = $this->tokens[$x]['type'];
×
2466
                                Common::printStatusMessage("* cleaned $x ($type) *", 2);
×
2467
                            }
2468
                        }
2469
                    }
2470
                }//end if
2471

2472
                continue;
1,167✔
2473
            } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1,443✔
2474
                /*
2475
                    Detect anonymous classes and assign them a different token.
2476
                */
2477

2478
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,209✔
2479
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,209✔
2480
                        break;
1,209✔
2481
                    }
2482
                }
2483

2484
                if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,209✔
2485
                    || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,209✔
2486
                    || $this->tokens[$x]['code'] === T_EXTENDS
1,182✔
2487
                    || $this->tokens[$x]['code'] === T_IMPLEMENTS
1,209✔
2488
                ) {
2489
                    $this->tokens[$i]['code'] = T_ANON_CLASS;
1,041✔
2490
                    $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1,041✔
2491
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,041✔
2492
                        $line = $this->tokens[$i]['line'];
×
2493
                        Common::printStatusMessage("* token $i on line $line changed from T_CLASS to T_ANON_CLASS", 1);
×
2494
                    }
2495

2496
                    if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,041✔
2497
                        && isset($this->tokens[$x]['parenthesis_closer']) === true
1,041✔
2498
                    ) {
2499
                        $closer = $this->tokens[$x]['parenthesis_closer'];
645✔
2500

2501
                        $this->tokens[$i]['parenthesis_opener']     = $x;
645✔
2502
                        $this->tokens[$i]['parenthesis_closer']     = $closer;
645✔
2503
                        $this->tokens[$i]['parenthesis_owner']      = $i;
645✔
2504
                        $this->tokens[$x]['parenthesis_owner']      = $i;
645✔
2505
                        $this->tokens[$closer]['parenthesis_owner'] = $i;
645✔
2506

2507
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
645✔
2508
                            $line = $this->tokens[$i]['line'];
×
2509
                            Common::printStatusMessage("* added parenthesis keys to T_ANON_CLASS token $i on line $line", 2);
×
2510
                        }
2511
                    }
2512

2513
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1,041✔
2514
                        if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1,041✔
2515
                            continue;
×
2516
                        }
2517

2518
                        $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1,041✔
2519
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,041✔
2520
                            $type = $this->tokens[$x]['type'];
×
2521
                            Common::printStatusMessage("* cleaned $x ($type) *", 2);
×
2522
                        }
2523
                    }
2524
                }//end if
2525

2526
                continue;
1,209✔
2527
            } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1,443✔
2528
                // Possible arrow function.
2529
                for ($x = ($i + 1); $x < $numTokens; $x++) {
936✔
2530
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
936✔
2531
                        && $this->tokens[$x]['code'] !== T_BITWISE_AND
936✔
2532
                    ) {
2533
                        // Non-whitespace content.
2534
                        break;
936✔
2535
                    }
2536
                }
2537

2538
                if (isset($this->tokens[$x]) === true && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
936✔
2539
                    $ignore  = Tokens::$emptyTokens;
936✔
2540
                    $ignore += [
624✔
2541
                        T_ARRAY                  => T_ARRAY,
936✔
2542
                        T_CALLABLE               => T_CALLABLE,
936✔
2543
                        T_COLON                  => T_COLON,
936✔
2544
                        T_NAME_FULLY_QUALIFIED   => T_NAME_FULLY_QUALIFIED,
936✔
2545
                        T_NAME_QUALIFIED         => T_NAME_QUALIFIED,
936✔
2546
                        T_NAME_RELATIVE          => T_NAME_RELATIVE,
936✔
2547
                        T_NULL                   => T_NULL,
936✔
2548
                        T_TRUE                   => T_TRUE,
936✔
2549
                        T_FALSE                  => T_FALSE,
936✔
2550
                        T_NULLABLE               => T_NULLABLE,
936✔
2551
                        T_PARENT                 => T_PARENT,
936✔
2552
                        T_SELF                   => T_SELF,
936✔
2553
                        T_STATIC                 => T_STATIC,
936✔
2554
                        T_STRING                 => T_STRING,
936✔
2555
                        T_TYPE_UNION             => T_TYPE_UNION,
936✔
2556
                        T_TYPE_INTERSECTION      => T_TYPE_INTERSECTION,
936✔
2557
                        T_TYPE_OPEN_PARENTHESIS  => T_TYPE_OPEN_PARENTHESIS,
936✔
2558
                        T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
936✔
2559
                    ];
624✔
2560

2561
                    $closer = $this->tokens[$x]['parenthesis_closer'];
936✔
2562
                    for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
936✔
2563
                        if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
936✔
2564
                            break;
936✔
2565
                        }
2566
                    }
2567

2568
                    if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
936✔
2569
                        $endTokens = [
624✔
2570
                            T_COLON                => true,
936✔
2571
                            T_COMMA                => true,
936✔
2572
                            T_SEMICOLON            => true,
936✔
2573
                            T_CLOSE_PARENTHESIS    => true,
936✔
2574
                            T_CLOSE_SQUARE_BRACKET => true,
936✔
2575
                            T_CLOSE_CURLY_BRACKET  => true,
936✔
2576
                            T_CLOSE_SHORT_ARRAY    => true,
936✔
2577
                            T_OPEN_TAG             => true,
936✔
2578
                            T_CLOSE_TAG            => true,
936✔
2579
                        ];
624✔
2580

2581
                        $inTernary    = false;
936✔
2582
                        $lastEndToken = null;
936✔
2583

2584
                        for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
936✔
2585
                            // Arrow function closer should never be shared with the closer of a match
2586
                            // control structure.
2587
                            if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
936✔
2588
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
936✔
2589
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
936✔
2590
                            ) {
2591
                                if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
414✔
2592
                                    // Match in return value of arrow function. Move on to the next token.
2593
                                    continue;
414✔
2594
                                }
2595

2596
                                // Arrow function as return value for the last match case without trailing comma.
2597
                                if ($lastEndToken !== null) {
414✔
2598
                                    $scopeCloser = $lastEndToken;
414✔
2599
                                    break;
414✔
2600
                                }
2601

2602
                                for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2603
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2604
                                        $scopeCloser = $lastNonEmpty;
186✔
2605
                                        break 2;
186✔
2606
                                    }
2607
                                }
2608
                            }
2609

2610
                            if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
936✔
2611
                                if ($lastEndToken !== null
936✔
2612
                                    && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true
804✔
2613
                                    && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow)
686✔
2614
                                    || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true
804✔
2615
                                    && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow))
936✔
2616
                                ) {
2617
                                    for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
186✔
2618
                                        if (isset(Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
186✔
2619
                                            $scopeCloser = $lastNonEmpty;
186✔
2620
                                            break;
186✔
2621
                                        }
2622
                                    }
2623
                                }
2624

2625
                                break;
936✔
2626
                            }
2627

2628
                            if ($inTernary === false
936✔
2629
                                && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
936✔
2630
                                && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
936✔
2631
                                && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN
936✔
2632
                            ) {
2633
                                // Found a nested arrow function that already has the closer set and is in
2634
                                // the same scope as us, so we can use its closer.
2635
                                break;
186✔
2636
                            }
2637

2638
                            if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
936✔
2639
                                && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
936✔
2640
                                && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC
936✔
2641
                                && $this->tokens[$scopeCloser]['code'] !== T_END_NOWDOC
936✔
2642
                            ) {
2643
                                // We minus 1 here in case the closer can be shared with us.
2644
                                $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
414✔
2645
                                continue;
414✔
2646
                            }
2647

2648
                            if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
936✔
2649
                                $scopeCloser  = $this->tokens[$scopeCloser]['parenthesis_closer'];
540✔
2650
                                $lastEndToken = $scopeCloser;
540✔
2651
                                continue;
540✔
2652
                            }
2653

2654
                            if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
936✔
2655
                                $scopeCloser  = $this->tokens[$scopeCloser]['bracket_closer'];
186✔
2656
                                $lastEndToken = $scopeCloser;
186✔
2657
                                continue;
186✔
2658
                            }
2659

2660
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
936✔
2661
                                $inTernary = true;
186✔
2662
                                continue;
186✔
2663
                            }
2664

2665
                            if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
936✔
2666
                                if ($inTernary === false) {
186✔
2667
                                    break;
186✔
2668
                                }
2669

2670
                                $inTernary = false;
186✔
2671
                                continue;
186✔
2672
                            }
2673
                        }//end for
2674

2675
                        if ($scopeCloser !== $numTokens) {
936✔
2676
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
936✔
2677
                                $line = $this->tokens[$i]['line'];
×
2678
                                Common::printStatusMessage("=> token $i on line $line processed as arrow function", 1);
×
2679
                                Common::printStatusMessage("* scope opener set to $arrow *", 2);
×
2680
                                Common::printStatusMessage("* scope closer set to $scopeCloser *", 2);
×
2681
                                Common::printStatusMessage("* parenthesis opener set to $x *", 2);
×
2682
                                Common::printStatusMessage("* parenthesis closer set to $closer *", 2);
×
2683
                            }
2684

2685
                            $this->tokens[$i]['code']            = T_FN;
936✔
2686
                            $this->tokens[$i]['type']            = 'T_FN';
936✔
2687
                            $this->tokens[$i]['scope_condition'] = $i;
936✔
2688
                            $this->tokens[$i]['scope_opener']    = $arrow;
936✔
2689
                            $this->tokens[$i]['scope_closer']    = $scopeCloser;
936✔
2690
                            $this->tokens[$i]['parenthesis_owner']  = $i;
936✔
2691
                            $this->tokens[$i]['parenthesis_opener'] = $x;
936✔
2692
                            $this->tokens[$i]['parenthesis_closer'] = $closer;
936✔
2693

2694
                            $this->tokens[$arrow]['code'] = T_FN_ARROW;
936✔
2695
                            $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
936✔
2696

2697
                            $this->tokens[$arrow]['scope_condition']       = $i;
936✔
2698
                            $this->tokens[$arrow]['scope_opener']          = $arrow;
936✔
2699
                            $this->tokens[$arrow]['scope_closer']          = $scopeCloser;
936✔
2700
                            $this->tokens[$scopeCloser]['scope_condition'] = $i;
936✔
2701
                            $this->tokens[$scopeCloser]['scope_opener']    = $arrow;
936✔
2702
                            $this->tokens[$scopeCloser]['scope_closer']    = $scopeCloser;
936✔
2703

2704
                            $opener = $this->tokens[$i]['parenthesis_opener'];
936✔
2705
                            $closer = $this->tokens[$i]['parenthesis_closer'];
936✔
2706
                            $this->tokens[$opener]['parenthesis_owner'] = $i;
936✔
2707
                            $this->tokens[$closer]['parenthesis_owner'] = $i;
936✔
2708

2709
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
936✔
2710
                                $line = $this->tokens[$arrow]['line'];
×
2711
                                Common::printStatusMessage("* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW", 2);
×
2712
                            }
2713
                        }//end if
2714
                    }//end if
2715
                }//end if
2716

2717
                // If after all that, the extra tokens are not set, this is not an arrow function.
2718
                if (isset($this->tokens[$i]['scope_closer']) === false) {
936✔
2719
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
582✔
2720
                        $line    = $this->tokens[$i]['line'];
×
2721
                        $oldCode = $this->tokens[$i]['code'];
×
2722
                        Common::printStatusMessage("* token $i on line $line changed from $oldCode to T_STRING: not an arrow function after all", 2);
×
2723
                    }
2724

2725
                    $this->tokens[$i]['code'] = T_STRING;
582✔
2726
                    $this->tokens[$i]['type'] = 'T_STRING';
818✔
2727
                }
2728
            } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1,443✔
2729
                if (isset($this->tokens[$i]['bracket_closer']) === false) {
1,128✔
2730
                    continue;
99✔
2731
                }
2732

2733
                // Unless there is a variable or a bracket before this token,
2734
                // it is the start of an array being defined using the short syntax.
2735
                $isShortArray = false;
1,128✔
2736
                $allowed      = [
752✔
2737
                    T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
1,128✔
2738
                    T_CLOSE_CURLY_BRACKET      => T_CLOSE_CURLY_BRACKET,
1,128✔
2739
                    T_CLOSE_PARENTHESIS        => T_CLOSE_PARENTHESIS,
1,128✔
2740
                    T_VARIABLE                 => T_VARIABLE,
1,128✔
2741
                    T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
1,128✔
2742
                    T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
1,128✔
2743
                    T_STRING                   => T_STRING,
1,128✔
2744
                    T_NAME_FULLY_QUALIFIED     => T_NAME_FULLY_QUALIFIED,
1,128✔
2745
                    T_NAME_RELATIVE            => T_NAME_RELATIVE,
1,128✔
2746
                    T_NAME_QUALIFIED           => T_NAME_QUALIFIED,
1,128✔
2747
                    T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1,128✔
2748
                    T_DOUBLE_QUOTED_STRING     => T_DOUBLE_QUOTED_STRING,
1,128✔
2749
                ];
752✔
2750
                $allowed     += Tokens::$magicConstants;
1,128✔
2751

2752
                for ($x = ($i - 1); $x >= 0; $x--) {
1,128✔
2753
                    // If we hit a scope opener, the statement has ended
2754
                    // without finding anything, so it's probably an array
2755
                    // using PHP 7.1 short list syntax.
2756
                    if (isset($this->tokens[$x]['scope_opener']) === true) {
1,128✔
2757
                        $isShortArray = true;
234✔
2758
                        break;
234✔
2759
                    }
2760

2761
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,128✔
2762
                        // Allow for control structures without braces.
2763
                        if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,128✔
2764
                            && isset($this->tokens[$x]['parenthesis_owner']) === true
1,128✔
2765
                            && isset(Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
99✔
2766
                            || isset($allowed[$this->tokens[$x]['code']]) === false
1,128✔
2767
                        ) {
2768
                            $isShortArray = true;
1,128✔
2769
                        }
2770

2771
                        break;
1,128✔
2772
                    }
2773
                }//end for
2774

2775
                if ($isShortArray === true) {
1,128✔
2776
                    $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1,128✔
2777
                    $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1,128✔
2778

2779
                    $closer = $this->tokens[$i]['bracket_closer'];
1,128✔
2780
                    $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1,128✔
2781
                    $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1,128✔
2782
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,128✔
2783
                        $line = $this->tokens[$i]['line'];
×
2784
                        Common::printStatusMessage("* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY", 1);
×
2785
                        $line = $this->tokens[$closer]['line'];
×
2786
                        Common::printStatusMessage("* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY", 1);
×
2787
                    }
2788
                }
2789

2790
                continue;
1,128✔
2791
            } else if ($this->tokens[$i]['code'] === T_MATCH) {
1,443✔
2792
                if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
582✔
2793
                    // Not a match expression after all.
2794
                    $this->tokens[$i]['code'] = T_STRING;
93✔
2795
                    $this->tokens[$i]['type'] = 'T_STRING';
93✔
2796

2797
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
93✔
2798
                        Common::printStatusMessage("* token $i changed from T_MATCH to T_STRING", 1);
×
2799
                    }
2800

2801
                    if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
93✔
2802
                        $opener = $this->tokens[$i]['parenthesis_opener'];
93✔
2803
                        $closer = $this->tokens[$i]['parenthesis_closer'];
93✔
2804
                        unset(
62✔
2805
                            $this->tokens[$opener]['parenthesis_owner'],
93✔
2806
                            $this->tokens[$closer]['parenthesis_owner']
93✔
2807
                        );
62✔
2808
                        unset(
62✔
2809
                            $this->tokens[$i]['parenthesis_opener'],
93✔
2810
                            $this->tokens[$i]['parenthesis_closer'],
93✔
2811
                            $this->tokens[$i]['parenthesis_owner']
93✔
2812
                        );
62✔
2813

2814
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
93✔
2815
                            Common::printStatusMessage("* cleaned parenthesis of token $i *", 2);
62✔
2816
                        }
2817
                    }
2818
                } else {
2819
                    // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
2820
                    $searchFor  = [
388✔
2821
                        T_OPEN_CURLY_BRACKET  => T_OPEN_CURLY_BRACKET,
582✔
2822
                        T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
582✔
2823
                        T_OPEN_PARENTHESIS    => T_OPEN_PARENTHESIS,
582✔
2824
                        T_OPEN_SHORT_ARRAY    => T_OPEN_SHORT_ARRAY,
582✔
2825
                        T_DOUBLE_ARROW        => T_DOUBLE_ARROW,
582✔
2826
                    ];
388✔
2827
                    $searchFor += Tokens::$scopeOpeners;
582✔
2828

2829
                    for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
582✔
2830
                        if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
582✔
2831
                            continue;
582✔
2832
                        }
2833

2834
                        if (isset($this->tokens[$x]['scope_closer']) === true) {
456✔
2835
                            $x = $this->tokens[$x]['scope_closer'];
270✔
2836
                            continue;
270✔
2837
                        }
2838

2839
                        if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
456✔
2840
                            $x = $this->tokens[$x]['parenthesis_closer'];
456✔
2841
                            continue;
456✔
2842
                        }
2843

2844
                        if (isset($this->tokens[$x]['bracket_closer']) === true) {
456✔
2845
                            $x = $this->tokens[$x]['bracket_closer'];
270✔
2846
                            continue;
270✔
2847
                        }
2848

2849
                        // This must be a double arrow, but make sure anyhow.
2850
                        if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
456✔
2851
                            $this->tokens[$x]['code'] = T_MATCH_ARROW;
456✔
2852
                            $this->tokens[$x]['type'] = 'T_MATCH_ARROW';
456✔
2853

2854
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
456✔
2855
                                Common::printStatusMessage("* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW", 1);
×
2856
                            }
2857
                        }
2858
                    }//end for
2859
                }//end if
2860

2861
                continue;
582✔
2862
            } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
1,443✔
2863
                || $this->tokens[$i]['code'] === T_BITWISE_AND
1,443✔
2864
                || $this->tokens[$i]['code'] === T_OPEN_PARENTHESIS
1,443✔
2865
                || $this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,443✔
2866
            ) {
2867
                /*
2868
                    Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
2869
                    Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
2870
                    Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
2871

2872
                    All type related tokens will be converted in one go as soon as this section is hit.
2873
                */
2874

2875
                $allowed = [
962✔
2876
                    T_STRING               => T_STRING,
1,443✔
2877
                    T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED,
1,443✔
2878
                    T_NAME_RELATIVE        => T_NAME_RELATIVE,
1,443✔
2879
                    T_NAME_QUALIFIED       => T_NAME_QUALIFIED,
1,443✔
2880
                    T_CALLABLE             => T_CALLABLE,
1,443✔
2881
                    T_SELF                 => T_SELF,
1,443✔
2882
                    T_PARENT               => T_PARENT,
1,443✔
2883
                    T_STATIC               => T_STATIC,
1,443✔
2884
                    T_FALSE                => T_FALSE,
1,443✔
2885
                    T_TRUE                 => T_TRUE,
1,443✔
2886
                    T_NULL                 => T_NULL,
1,443✔
2887
                ];
962✔
2888

2889
                $suspectedType       = null;
1,443✔
2890
                $typeTokenCountAfter = 0;
1,443✔
2891

2892
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,443✔
2893
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,443✔
2894
                        continue;
1,443✔
2895
                    }
2896

2897
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,443✔
2898
                        ++$typeTokenCountAfter;
1,416✔
2899
                        continue;
1,416✔
2900
                    }
2901

2902
                    if (($typeTokenCountAfter > 0
1,443✔
2903
                        || ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1,443✔
2904
                        && isset($this->tokens[$i]['parenthesis_owner']) === false))
1,443✔
2905
                        && ($this->tokens[$x]['code'] === T_BITWISE_AND
1,443✔
2906
                        || $this->tokens[$x]['code'] === T_ELLIPSIS)
1,443✔
2907
                    ) {
2908
                        // Skip past reference and variadic indicators for parameter types.
2909
                        continue;
684✔
2910
                    }
2911

2912
                    if ($this->tokens[$x]['code'] === T_VARIABLE) {
1,443✔
2913
                        // Parameter/Property defaults can not contain variables, so this could be a type.
2914
                        $suspectedType = 'property or parameter';
1,140✔
2915
                        break;
1,140✔
2916
                    }
2917

2918
                    if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
1,443✔
2919
                        // Possible arrow function.
2920
                        $suspectedType = 'return';
936✔
2921
                        break;
936✔
2922
                    }
2923

2924
                    if ($this->tokens[$x]['code'] === T_SEMICOLON) {
1,443✔
2925
                        // Possible abstract method or interface method.
2926
                        $suspectedType = 'return';
1,416✔
2927
                        break;
1,416✔
2928
                    }
2929

2930
                    if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1,443✔
2931
                        && isset($this->tokens[$x]['scope_condition']) === true
1,443✔
2932
                        && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION
1,443✔
2933
                    ) {
2934
                        $suspectedType = 'return';
1,167✔
2935
                        break;
1,167✔
2936
                    }
2937

2938
                    if ($this->tokens[$x]['code'] === T_EQUAL) {
1,443✔
2939
                        // Possible constant declaration, the `T_STRING` name will have been skipped over already.
2940
                        $suspectedType = 'constant';
834✔
2941
                        break;
834✔
2942
                    }
2943

2944
                    break;
1,443✔
2945
                }//end for
2946

2947
                if (($typeTokenCountAfter === 0
1,443✔
2948
                    && ($this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
1,443✔
2949
                    || isset($this->tokens[$i]['parenthesis_owner']) === true))
1,443✔
2950
                    || isset($suspectedType) === false
1,443✔
2951
                ) {
2952
                    // Definitely not a union, intersection or DNF type, move on.
2953
                    continue;
1,443✔
2954
                }
2955

2956
                if ($suspectedType === 'property or parameter') {
1,416✔
2957
                    unset($allowed[T_STATIC]);
870✔
2958
                }
2959

2960
                $typeTokenCountBefore = 0;
1,416✔
2961
                $typeOperators        = [$i];
1,416✔
2962
                $confirmed            = false;
1,416✔
2963
                $maybeNullable        = null;
1,416✔
2964

2965
                for ($x = ($i - 1); $x >= 0; $x--) {
1,416✔
2966
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1,416✔
2967
                        continue;
1,140✔
2968
                    }
2969

2970
                    if ($suspectedType === 'property or parameter'
1,416✔
2971
                        && $this->tokens[$x]['code'] === T_STRING
1,416✔
2972
                        && strtolower($this->tokens[$x]['content']) === 'static'
1,416✔
2973
                    ) {
2974
                        // Static keyword followed directly by an open parenthesis for a DNF type.
2975
                        // This token should be T_STATIC and was incorrectly identified as a function call before.
2976
                        $this->tokens[$x]['code'] = T_STATIC;
126✔
2977
                        $this->tokens[$x]['type'] = 'T_STATIC';
126✔
2978

2979
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
126✔
NEW
2980
                            $line = $this->tokens[$x]['line'];
×
NEW
2981
                            echo "\t* token $x on line $line changed back from T_STRING to T_STATIC".PHP_EOL;
×
2982
                        }
2983
                    }
2984

2985
                    if ($suspectedType === 'property or parameter'
1,416✔
2986
                        && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,416✔
2987
                    ) {
2988
                        // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
2989
                        // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
2990
                        if (isset($this->tokens[$x]['parenthesis_owner']) === true
870✔
2991
                            && $this->tokens[$this->tokens[$x]['parenthesis_owner']]['code'] === T_FUNCTION
870✔
2992
                        ) {
2993
                            $confirmed = true;
870✔
2994
                            break;
870✔
2995
                        } else {
2996
                            // This may still be an arrow function which hasn't be handled yet.
2997
                            for ($y = ($x - 1); $y > 0; $y--) {
870✔
2998
                                if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false
870✔
2999
                                    && $this->tokens[$y]['code'] !== T_BITWISE_AND
870✔
3000
                                ) {
3001
                                    // Non-whitespace content.
3002
                                    break;
870✔
3003
                                }
3004
                            }
3005

3006
                            if ($this->tokens[$y]['code'] === T_FN) {
870✔
3007
                                $confirmed = true;
708✔
3008
                                break;
708✔
3009
                            }
3010
                        }
3011
                    }//end if
3012

3013
                    if (isset($allowed[$this->tokens[$x]['code']]) === true) {
1,416✔
3014
                        ++$typeTokenCountBefore;
1,140✔
3015
                        continue;
1,140✔
3016
                    }
3017

3018
                    // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3019
                    if (($typeTokenCountBefore > 0
1,416✔
3020
                        || ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS && isset($this->tokens[$x]['parenthesis_owner']) === false))
1,416✔
3021
                        && ($this->tokens[$x]['code'] === T_NULLABLE
1,324✔
3022
                        || $this->tokens[$x]['code'] === T_INLINE_THEN)
1,416✔
3023
                    ) {
3024
                        if ($this->tokens[$x]['code'] === T_INLINE_THEN) {
699✔
3025
                            $maybeNullable = $x;
126✔
3026
                        }
3027

3028
                        continue;
699✔
3029
                    }
3030

3031
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR
1,416✔
3032
                        || $this->tokens[$x]['code'] === T_BITWISE_AND
1,416✔
3033
                        || $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1,416✔
3034
                        || $this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
1,416✔
3035
                    ) {
3036
                        $typeOperators[] = $x;
1,140✔
3037
                        continue;
1,140✔
3038
                    }
3039

3040
                    if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
1,416✔
3041
                        $confirmed = true;
708✔
3042
                        break;
708✔
3043
                    }
3044

3045
                    if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
1,416✔
3046
                        $confirmed = true;
699✔
3047
                        break;
699✔
3048
                    }
3049

3050
                    if ($suspectedType === 'property or parameter'
1,416✔
3051
                        && (isset(Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
1,234✔
3052
                        || $this->tokens[$x]['code'] === T_VAR
1,234✔
3053
                        || $this->tokens[$x]['code'] === T_STATIC
1,234✔
3054
                        || $this->tokens[$x]['code'] === T_READONLY)
1,416✔
3055
                    ) {
3056
                        // This will also confirm constructor property promotion parameters, but that's fine.
3057
                        $confirmed = true;
684✔
3058
                    }
3059

3060
                    break;
1,416✔
3061
                }//end for
3062

3063
                if ($confirmed === false
1,416✔
3064
                    && $suspectedType === 'property or parameter'
1,416✔
3065
                    && isset($this->tokens[$i]['nested_parenthesis']) === true
1,416✔
3066
                ) {
3067
                    $parens = $this->tokens[$i]['nested_parenthesis'];
708✔
3068
                    $last   = end($parens);
708✔
3069

3070
                    if (isset($this->tokens[$last]['parenthesis_owner']) === true
708✔
3071
                        && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION
708✔
3072
                    ) {
3073
                        $confirmed = true;
522✔
3074
                    } else {
3075
                        // No parenthesis owner set, this may be an arrow function which has not yet
3076
                        // had additional processing done.
3077
                        if (isset($this->tokens[$last]['parenthesis_opener']) === true) {
390✔
3078
                            for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) {
390✔
3079
                                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
390✔
3080
                                    continue;
204✔
3081
                                }
3082

3083
                                break;
390✔
3084
                            }
3085

3086
                            if ($this->tokens[$x]['code'] === T_FN) {
390✔
UNCOV
3087
                                for (--$x; $x >= 0; $x--) {
×
UNCOV
3088
                                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true
×
UNCOV
3089
                                        || $this->tokens[$x]['code'] === T_BITWISE_AND
×
3090
                                    ) {
UNCOV
3091
                                        continue;
×
3092
                                    }
3093

UNCOV
3094
                                    break;
×
3095
                                }
3096

UNCOV
3097
                                if ($this->tokens[$x]['code'] !== T_FUNCTION) {
×
UNCOV
3098
                                    $confirmed = true;
×
3099
                                }
3100
                            }
3101
                        }//end if
3102
                    }//end if
3103

3104
                    unset($parens, $last);
708✔
3105
                }//end if
3106

3107
                if ($confirmed === false) {
1,416✔
3108
                    // Not a union or intersection type after all, move on.
3109
                    continue;
1,416✔
3110
                }
3111

3112
                foreach ($typeOperators as $x) {
1,047✔
3113
                    if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
1,047✔
3114
                        $this->tokens[$x]['code'] = T_TYPE_UNION;
1,047✔
3115
                        $this->tokens[$x]['type'] = 'T_TYPE_UNION';
1,047✔
3116

3117
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,047✔
3118
                            $line = $this->tokens[$x]['line'];
×
3119
                            Common::printStatusMessage("* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION", 1);
698✔
3120
                        }
3121
                    } else if ($this->tokens[$x]['code'] === T_BITWISE_AND) {
1,047✔
3122
                        $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
1,047✔
3123
                        $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
1,047✔
3124

3125
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,047✔
3126
                            $line = $this->tokens[$x]['line'];
×
3127
                            Common::printStatusMessage("* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION", 1);
698✔
3128
                        }
3129
                    } else if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1,047✔
3130
                        $this->tokens[$x]['code'] = T_TYPE_OPEN_PARENTHESIS;
1,047✔
3131
                        $this->tokens[$x]['type'] = 'T_TYPE_OPEN_PARENTHESIS';
1,047✔
3132

3133
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,047✔
NEW
3134
                            $line = $this->tokens[$x]['line'];
×
3135
                            echo "\t* token $x on line $line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS".PHP_EOL;
698✔
3136
                        }
3137
                    } else if ($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS) {
1,047✔
3138
                        $this->tokens[$x]['code'] = T_TYPE_CLOSE_PARENTHESIS;
1,047✔
3139
                        $this->tokens[$x]['type'] = 'T_TYPE_CLOSE_PARENTHESIS';
1,047✔
3140

3141
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,047✔
NEW
3142
                            $line = $this->tokens[$x]['line'];
×
NEW
3143
                            echo "\t* token $x on line $line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS".PHP_EOL;
×
3144
                        }
3145
                    }//end if
3146
                }//end foreach
3147

3148
                if (isset($maybeNullable) === true) {
1,047✔
3149
                    $this->tokens[$maybeNullable]['code'] = T_NULLABLE;
126✔
3150
                    $this->tokens[$maybeNullable]['type'] = 'T_NULLABLE';
126✔
3151

3152
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
126✔
NEW
3153
                        $line = $this->tokens[$maybeNullable]['line'];
×
NEW
3154
                        echo "\t* token $maybeNullable on line $line changed from T_INLINE_THEN to T_NULLABLE".PHP_EOL;
×
3155
                    }
3156
                }
3157

3158
                continue;
1,047✔
3159
            } else if ($this->tokens[$i]['code'] === T_STATIC) {
1,443✔
3160
                for ($x = ($i - 1); $x > 0; $x--) {
1,140✔
3161
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,140✔
3162
                        break;
1,140✔
3163
                    }
3164
                }
3165

3166
                if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
1,140✔
3167
                    $this->tokens[$i]['code'] = T_STRING;
×
3168
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3169

3170
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3171
                        $line = $this->tokens[$i]['line'];
×
3172
                        Common::printStatusMessage("* token $i on line $line changed from T_STATIC to T_STRING", 1);
×
3173
                    }
3174
                }
3175

3176
                continue;
1,140✔
3177
            } else if ($this->tokens[$i]['code'] === T_TRUE
1,443✔
3178
                || $this->tokens[$i]['code'] === T_FALSE
1,443✔
3179
                || $this->tokens[$i]['code'] === T_NULL
1,443✔
3180
            ) {
3181
                for ($x = ($i + 1); $x < $numTokens; $x++) {
1,374✔
3182
                    if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1,374✔
3183
                        // Non-whitespace content.
3184
                        break;
1,374✔
3185
                    }
3186
                }
3187

3188
                if ($x !== $numTokens
1,374✔
3189
                    && isset($this->tstringContexts[$this->tokens[$x]['code']]) === true
1,374✔
3190
                ) {
3191
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3192
                        $line = $this->tokens[$i]['line'];
×
3193
                        $type = $this->tokens[$i]['type'];
×
3194
                        Common::printStatusMessage("* token $i on line $line changed from $type to T_STRING", 1);
×
3195
                    }
3196

3197
                    $this->tokens[$i]['code'] = T_STRING;
×
3198
                    $this->tokens[$i]['type'] = 'T_STRING';
×
3199
                }
3200
            }//end if
3201

3202
            if (($this->tokens[$i]['code'] !== T_CASE
1,443✔
3203
                && $this->tokens[$i]['code'] !== T_DEFAULT)
1,443✔
3204
                || isset($this->tokens[$i]['scope_opener']) === false
1,443✔
3205
            ) {
3206
                // Only interested in CASE and DEFAULT statements from here on in.
3207
                continue;
1,443✔
3208
            }
3209

3210
            $scopeOpener = $this->tokens[$i]['scope_opener'];
135✔
3211
            $scopeCloser = $this->tokens[$i]['scope_closer'];
135✔
3212

3213
            // If the first char after the opener is a curly brace
3214
            // and that brace has been ignored, it is actually
3215
            // opening this case statement and the opener and closer are
3216
            // probably set incorrectly.
3217
            for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
135✔
3218
                if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
135✔
3219
                    // Non-whitespace content.
3220
                    break;
135✔
3221
                }
3222
            }
3223

3224
            if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
135✔
3225
                // Special case for multiple CASE statements that share the same
3226
                // closer. Because we are going backwards through the file, this next
3227
                // CASE statement is already fixed, so just use its closer and don't
3228
                // worry about fixing anything.
3229
                $newCloser = $this->tokens[$x]['scope_closer'];
×
3230
                $this->tokens[$i]['scope_closer'] = $newCloser;
×
3231
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3232
                    $oldType = $this->tokens[$scopeCloser]['type'];
×
3233
                    $newType = $this->tokens[$newCloser]['type'];
×
3234
                    $line    = $this->tokens[$i]['line'];
×
3235
                    Common::printStatusMessage("* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3236
                }
3237

3238
                continue;
×
3239
            }
3240

3241
            if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
135✔
3242
                || isset($this->tokens[$x]['scope_condition']) === true
135✔
3243
            ) {
3244
                // Not a CASE/DEFAULT with a curly brace opener.
3245
                continue;
135✔
3246
            }
3247

3248
            // The closer for this CASE/DEFAULT should be the closing curly brace and
3249
            // not whatever it already is. The opener needs to be the opening curly
3250
            // brace so everything matches up.
3251
            $newCloser = $this->tokens[$x]['bracket_closer'];
42✔
3252
            foreach ([$i, $x, $newCloser] as $index) {
42✔
3253
                $this->tokens[$index]['scope_condition'] = $i;
42✔
3254
                $this->tokens[$index]['scope_opener']    = $x;
42✔
3255
                $this->tokens[$index]['scope_closer']    = $newCloser;
42✔
3256
            }
3257

3258
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
42✔
3259
                $line      = $this->tokens[$i]['line'];
×
3260
                $tokenType = $this->tokens[$i]['type'];
×
3261

3262
                $oldType = $this->tokens[$scopeOpener]['type'];
×
3263
                $newType = $this->tokens[$x]['type'];
×
3264
                Common::printStatusMessage("* token $i ($tokenType) on line $line opener changed from $scopeOpener ($oldType) to $x ($newType)", 1);
×
3265

3266
                $oldType = $this->tokens[$scopeCloser]['type'];
×
3267
                $newType = $this->tokens[$newCloser]['type'];
×
3268
                Common::printStatusMessage("* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 1);
×
3269
            }
3270

3271
            if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
42✔
3272
                unset($this->tokens[$scopeOpener]['scope_condition']);
42✔
3273
                unset($this->tokens[$scopeOpener]['scope_opener']);
42✔
3274
                unset($this->tokens[$scopeOpener]['scope_closer']);
42✔
3275
            }
3276

3277
            if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
42✔
3278
                unset($this->tokens[$scopeCloser]['scope_condition']);
42✔
3279
                unset($this->tokens[$scopeCloser]['scope_opener']);
42✔
3280
                unset($this->tokens[$scopeCloser]['scope_closer']);
42✔
3281
            } else {
3282
                // We were using a shared closer. All tokens that were
3283
                // sharing this closer with us, except for the scope condition
3284
                // and it's opener, need to now point to the new closer.
3285
                $condition = $this->tokens[$scopeCloser]['scope_condition'];
×
3286
                $start     = ($this->tokens[$condition]['scope_opener'] + 1);
×
3287
                for ($y = $start; $y < $scopeCloser; $y++) {
×
3288
                    if (isset($this->tokens[$y]['scope_closer']) === true
×
3289
                        && $this->tokens[$y]['scope_closer'] === $scopeCloser
×
3290
                    ) {
3291
                        $this->tokens[$y]['scope_closer'] = $newCloser;
×
3292

3293
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3294
                            $line      = $this->tokens[$y]['line'];
×
3295
                            $tokenType = $this->tokens[$y]['type'];
×
3296
                            $oldType   = $this->tokens[$scopeCloser]['type'];
×
3297
                            $newType   = $this->tokens[$newCloser]['type'];
×
3298
                            Common::printStatusMessage("* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)", 2);
×
3299
                        }
3300
                    }
3301
                }
3302
            }//end if
3303

3304
            unset($this->tokens[$x]['bracket_opener']);
42✔
3305
            unset($this->tokens[$x]['bracket_closer']);
42✔
3306
            unset($this->tokens[$newCloser]['bracket_opener']);
42✔
3307
            unset($this->tokens[$newCloser]['bracket_closer']);
42✔
3308
            $this->tokens[$scopeCloser]['conditions'][] = $i;
42✔
3309

3310
            // Now fix up all the tokens that think they are
3311
            // inside the CASE/DEFAULT statement when they are really outside.
3312
            for ($x = $newCloser; $x < $scopeCloser; $x++) {
42✔
3313
                foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
×
3314
                    if ($oldCond === $this->tokens[$i]['code']) {
×
3315
                        $oldConditions = $this->tokens[$x]['conditions'];
×
3316
                        unset($this->tokens[$x]['conditions'][$num]);
×
3317

3318
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
3319
                            $type     = $this->tokens[$x]['type'];
×
3320
                            $oldConds = '';
×
3321
                            foreach ($oldConditions as $condition) {
×
3322
                                $oldConds .= Tokens::tokenName($condition).',';
×
3323
                            }
3324

3325
                            $oldConds = rtrim($oldConds, ',');
×
3326

3327
                            $newConds = '';
×
3328
                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
3329
                                $newConds .= Tokens::tokenName($condition).',';
×
3330
                            }
3331

3332
                            $newConds = rtrim($newConds, ',');
×
3333

3334
                            Common::printStatusMessage("* cleaned $x ($type) *", 2);
×
3335
                            Common::printStatusMessage("=> conditions changed from $oldConds to $newConds", 3);
×
3336
                        }
3337

3338
                        break;
×
3339
                    }//end if
3340
                }//end foreach
3341
            }//end for
3342
        }//end for
3343

3344
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1,443✔
3345
            Common::printStatusMessage('*** END ADDITIONAL PHP PROCESSING ***', 1);
×
3346
        }
3347

3348
    }//end processAdditional()
481✔
3349

3350

3351
    /**
3352
     * Takes a token produced from <code>token_get_all()</code> and produces a
3353
     * more uniform token.
3354
     *
3355
     * @param string|array $token The token to convert.
3356
     *
3357
     * @return array The new token.
3358
     */
3359
    public static function standardiseToken($token)
3✔
3360
    {
3361
        if (isset($token[1]) === false) {
3✔
3362
            if (isset(self::$resolveTokenCache[$token[0]]) === true) {
3✔
3363
                return self::$resolveTokenCache[$token[0]];
2✔
3364
            }
3365
        } else {
3366
            $cacheKey = null;
3✔
3367
            if ($token[0] === T_STRING) {
3✔
3368
                $cacheKey = strtolower($token[1]);
3✔
3369
            } else if ($token[0] !== T_CURLY_OPEN) {
3✔
3370
                $cacheKey = $token[0];
3✔
3371
            }
3372

3373
            if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
3✔
3374
                $newToken            = self::$resolveTokenCache[$cacheKey];
×
3375
                $newToken['content'] = $token[1];
×
3376
                return $newToken;
×
3377
            }
3378
        }
3379

3380
        if (isset($token[1]) === false) {
3✔
3381
            return self::resolveSimpleToken($token[0]);
3✔
3382
        }
3383

3384
        if ($token[0] === T_STRING) {
3✔
3385
            switch ($cacheKey) {
1✔
3386
            case 'false':
3✔
3387
                $newToken['type'] = 'T_FALSE';
3✔
3388
                break;
3✔
3389
            case 'true':
3✔
3390
                $newToken['type'] = 'T_TRUE';
3✔
3391
                break;
3✔
3392
            case 'null':
3✔
3393
                $newToken['type'] = 'T_NULL';
3✔
3394
                break;
3✔
3395
            case 'self':
3✔
3396
                $newToken['type'] = 'T_SELF';
3✔
3397
                break;
3✔
3398
            case 'parent':
3✔
3399
                $newToken['type'] = 'T_PARENT';
3✔
3400
                break;
3✔
3401
            default:
3402
                $newToken['type'] = 'T_STRING';
3✔
3403
                break;
3✔
3404
            }
3405

3406
            $newToken['code'] = constant($newToken['type']);
3✔
3407

3408
            self::$resolveTokenCache[$cacheKey] = $newToken;
3✔
3409
        } else if ($token[0] === T_CURLY_OPEN) {
3✔
3410
            $newToken = [
3411
                'code' => T_OPEN_CURLY_BRACKET,
×
3412
                'type' => 'T_OPEN_CURLY_BRACKET',
×
3413
            ];
3414
        } else {
3415
            $newToken = [
2✔
3416
                'code' => $token[0],
3✔
3417
                'type' => Tokens::tokenName($token[0]),
3✔
3418
            ];
2✔
3419

3420
            self::$resolveTokenCache[$token[0]] = $newToken;
3✔
3421
        }//end if
3422

3423
        $newToken['content'] = $token[1];
3✔
3424
        return $newToken;
3✔
3425

3426
    }//end standardiseToken()
3427

3428

3429
    /**
3430
     * Converts simple tokens into a format that conforms to complex tokens
3431
     * produced by token_get_all().
3432
     *
3433
     * Simple tokens are tokens that are not in array form when produced from
3434
     * token_get_all().
3435
     *
3436
     * @param string $token The simple token to convert.
3437
     *
3438
     * @return array The new token in array format.
3439
     */
3440
    public static function resolveSimpleToken($token)
3✔
3441
    {
3442
        $newToken = [];
3✔
3443

3444
        switch ($token) {
1✔
3445
        case '{':
3✔
3446
            $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
3✔
3447
            break;
3✔
3448
        case '}':
3✔
3449
            $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
3✔
3450
            break;
3✔
3451
        case '[':
3✔
3452
            $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
3✔
3453
            break;
3✔
3454
        case ']':
3✔
3455
            $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
3✔
3456
            break;
3✔
3457
        case '(':
3✔
3458
            $newToken['type'] = 'T_OPEN_PARENTHESIS';
3✔
3459
            break;
3✔
3460
        case ')':
3✔
3461
            $newToken['type'] = 'T_CLOSE_PARENTHESIS';
3✔
3462
            break;
3✔
3463
        case ':':
3✔
3464
            $newToken['type'] = 'T_COLON';
3✔
3465
            break;
3✔
3466
        case '.':
3✔
3467
            $newToken['type'] = 'T_STRING_CONCAT';
3✔
3468
            break;
3✔
3469
        case ';':
3✔
3470
            $newToken['type'] = 'T_SEMICOLON';
3✔
3471
            break;
3✔
3472
        case '=':
3✔
3473
            $newToken['type'] = 'T_EQUAL';
3✔
3474
            break;
3✔
3475
        case '*':
3✔
3476
            $newToken['type'] = 'T_MULTIPLY';
3✔
3477
            break;
3✔
3478
        case '/':
3✔
3479
            $newToken['type'] = 'T_DIVIDE';
3✔
3480
            break;
3✔
3481
        case '+':
3✔
3482
            $newToken['type'] = 'T_PLUS';
3✔
3483
            break;
3✔
3484
        case '-':
3✔
3485
            $newToken['type'] = 'T_MINUS';
3✔
3486
            break;
3✔
3487
        case '%':
3✔
3488
            $newToken['type'] = 'T_MODULUS';
3✔
3489
            break;
3✔
3490
        case '^':
3✔
3491
            $newToken['type'] = 'T_BITWISE_XOR';
3✔
3492
            break;
3✔
3493
        case '&':
3✔
3494
            $newToken['type'] = 'T_BITWISE_AND';
1✔
3495
            break;
1✔
3496
        case '|':
3✔
3497
            $newToken['type'] = 'T_BITWISE_OR';
3✔
3498
            break;
3✔
3499
        case '~':
3✔
3500
            $newToken['type'] = 'T_BITWISE_NOT';
3✔
3501
            break;
3✔
3502
        case '<':
3✔
3503
            $newToken['type'] = 'T_LESS_THAN';
3✔
3504
            break;
3✔
3505
        case '>':
3✔
3506
            $newToken['type'] = 'T_GREATER_THAN';
3✔
3507
            break;
3✔
3508
        case '!':
3✔
3509
            $newToken['type'] = 'T_BOOLEAN_NOT';
3✔
3510
            break;
3✔
3511
        case ',':
3✔
3512
            $newToken['type'] = 'T_COMMA';
3✔
3513
            break;
3✔
3514
        case '@':
3✔
3515
            $newToken['type'] = 'T_ASPERAND';
3✔
3516
            break;
3✔
3517
        case '$':
3✔
3518
            $newToken['type'] = 'T_DOLLAR';
3✔
3519
            break;
3✔
3520
        case '`':
3✔
3521
            $newToken['type'] = 'T_BACKTICK';
3✔
3522
            break;
3✔
3523
        default:
3524
            $newToken['type'] = 'T_NONE';
×
3525
            break;
×
3526
        }//end switch
3527

3528
        $newToken['code']    = constant($newToken['type']);
3✔
3529
        $newToken['content'] = $token;
3✔
3530

3531
        self::$resolveTokenCache[$token] = $newToken;
3✔
3532
        return $newToken;
3✔
3533

3534
    }//end resolveSimpleToken()
3535

3536

3537
    /**
3538
     * Finds a "closer" token (closing parenthesis or square bracket for example)
3539
     * Handle parenthesis balancing while searching for closing token
3540
     *
3541
     * @param array           $tokens       The list of tokens to iterate searching the closing token (as returned by token_get_all)
3542
     * @param int             $start        The starting position
3543
     * @param string|string[] $openerTokens The opening character
3544
     * @param string          $closerChar   The closing character
3545
     *
3546
     * @return int|null The position of the closing token, if found. NULL otherwise.
3547
     */
3548
    private function findCloser(array &$tokens, $start, $openerTokens, $closerChar)
51✔
3549
    {
3550
        $numTokens    = count($tokens);
51✔
3551
        $stack        = [0];
51✔
3552
        $closer       = null;
51✔
3553
        $openerTokens = (array) $openerTokens;
51✔
3554

3555
        for ($x = $start; $x < $numTokens; $x++) {
51✔
3556
            if (in_array($tokens[$x], $openerTokens, true) === true
51✔
3557
                || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true)
51✔
3558
            ) {
3559
                $stack[] = $x;
51✔
3560
            } else if ($tokens[$x] === $closerChar) {
51✔
3561
                array_pop($stack);
51✔
3562
                if (empty($stack) === true) {
51✔
3563
                    $closer = $x;
51✔
3564
                    break;
51✔
3565
                }
3566
            }
3567
        }
3568

3569
        return $closer;
51✔
3570

3571
    }//end findCloser()
3572

3573

3574
    /**
3575
     * PHP 8 attributes parser for PHP < 8
3576
     * Handles single-line and multiline attributes.
3577
     *
3578
     * @param array $tokens   The original array of tokens (as returned by token_get_all)
3579
     * @param int   $stackPtr The current position in token array
3580
     *
3581
     * @return array|null The array of parsed attribute tokens
3582
     */
3583
    private function parsePhpAttribute(array &$tokens, $stackPtr)
19✔
3584
    {
3585

3586
        $token = $tokens[$stackPtr];
19✔
3587

3588
        $commentBody = substr($token[1], 2);
19✔
3589
        $subTokens   = @token_get_all('<?php '.$commentBody);
19✔
3590

3591
        foreach ($subTokens as $i => $subToken) {
19✔
3592
            if (is_array($subToken) === true
19✔
3593
                && $subToken[0] === T_COMMENT
19✔
3594
                && strpos($subToken[1], '#[') === 0
19✔
3595
            ) {
3596
                $reparsed = $this->parsePhpAttribute($subTokens, $i);
19✔
3597
                if ($reparsed !== null) {
19✔
3598
                    array_splice($subTokens, $i, 1, $reparsed);
19✔
3599
                } else {
3600
                    $subToken[0] = T_ATTRIBUTE;
×
3601
                }
3602
            }
3603
        }
3604

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

3607
        // Go looking for the close bracket.
3608
        $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3609
        if (PHP_VERSION_ID < 80000 && $bracketCloser === null) {
19✔
3610
            foreach (array_slice($tokens, ($stackPtr + 1)) as $token) {
19✔
3611
                if (is_array($token) === true) {
19✔
3612
                    $commentBody .= $token[1];
19✔
3613
                } else {
3614
                    $commentBody .= $token;
19✔
3615
                }
3616
            }
3617

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

3621
            $bracketCloser = $this->findCloser($subTokens, 1, '[', ']');
19✔
3622
            if ($bracketCloser !== null) {
19✔
3623
                array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1)));
19✔
3624
                $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1));
19✔
3625
            }
3626
        }
3627

3628
        if ($bracketCloser === null) {
19✔
3629
            return null;
19✔
3630
        }
3631

3632
        return $subTokens;
19✔
3633

3634
    }//end parsePhpAttribute()
3635

3636

3637
    /**
3638
     * Creates a map for the attributes tokens that surround other tokens.
3639
     *
3640
     * @return void
3641
     */
3642
    private function createAttributesNestingMap()
3✔
3643
    {
3644
        $map = [];
3✔
3645
        for ($i = 0; $i < $this->numTokens; $i++) {
3✔
3646
            if (isset($this->tokens[$i]['attribute_opener']) === true
3✔
3647
                && $i === $this->tokens[$i]['attribute_opener']
3✔
3648
            ) {
3649
                if (empty($map) === false) {
3✔
3650
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3651
                }
3652

3653
                if (isset($this->tokens[$i]['attribute_closer']) === true) {
3✔
3654
                    $map[$this->tokens[$i]['attribute_opener']]
3✔
3655
                        = $this->tokens[$i]['attribute_closer'];
3✔
3656
                }
3657
            } else if (isset($this->tokens[$i]['attribute_closer']) === true
3✔
3658
                && $i === $this->tokens[$i]['attribute_closer']
3✔
3659
            ) {
3660
                array_pop($map);
3✔
3661
                if (empty($map) === false) {
3✔
3662
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3663
                }
3664
            } else {
3665
                if (empty($map) === false) {
3✔
3666
                    $this->tokens[$i]['nested_attributes'] = $map;
3✔
3667
                }
3668
            }//end if
3669
        }//end for
3670

3671
    }//end createAttributesNestingMap()
1✔
3672

3673

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

© 2026 Coveralls, Inc