PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / extra / php-codesniffer-php-token / Tokenizers / PHP.php

PHP.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/extra/php-codesniffer-php-token/Tokenizers/PHP.php

2,374 lines 95.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Tokenizes PHP code.
4 *
5 * @author Greg Sherwood <[email protected]>
6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8 */
9
10 namespace PHP_CodeSniffer\Tokenizers;
11
12 use PHP_CodeSniffer\Util;
13
14 class PHP extends Tokenizer
15 {
16
17 /**
18 * A list of tokens that are allowed to open a scope.
19 *
20 * This array also contains information about what kind of token the scope
21 * opener uses to open and close the scope, if the token strictly requires
22 * an opener, if the token can share a scope closer, and who it can be shared
23 * with. An example of a token that shares a scope closer is a CASE scope.
24 *
25 * @var array
26 */
27 public $scopeOpeners = [
28 T_IF => [
29 'start' => [
30 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
31 T_COLON => T_COLON,
32 ],
33 'end' => [
34 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
35 T_ENDIF => T_ENDIF,
36 T_ELSE => T_ELSE,
37 T_ELSEIF => T_ELSEIF,
38 ],
39 'strict' => false,
40 'shared' => false,
41 'with' => [
42 T_ELSE => T_ELSE,
43 T_ELSEIF => T_ELSEIF,
44 ],
45 ],
46 T_TRY => [
47 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
48 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
49 'strict' => true,
50 'shared' => false,
51 'with' => [],
52 ],
53 T_CATCH => [
54 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
55 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
56 'strict' => true,
57 'shared' => false,
58 'with' => [],
59 ],
60 T_FINALLY => [
61 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
62 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
63 'strict' => true,
64 'shared' => false,
65 'with' => [],
66 ],
67 T_ELSE => [
68 'start' => [
69 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
70 T_COLON => T_COLON,
71 ],
72 'end' => [
73 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
74 T_ENDIF => T_ENDIF,
75 ],
76 'strict' => false,
77 'shared' => false,
78 'with' => [
79 T_IF => T_IF,
80 T_ELSEIF => T_ELSEIF,
81 ],
82 ],
83 T_ELSEIF => [
84 'start' => [
85 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
86 T_COLON => T_COLON,
87 ],
88 'end' => [
89 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
90 T_ENDIF => T_ENDIF,
91 T_ELSE => T_ELSE,
92 T_ELSEIF => T_ELSEIF,
93 ],
94 'strict' => false,
95 'shared' => false,
96 'with' => [
97 T_IF => T_IF,
98 T_ELSE => T_ELSE,
99 ],
100 ],
101 T_FOR => [
102 'start' => [
103 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
104 T_COLON => T_COLON,
105 ],
106 'end' => [
107 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
108 T_ENDFOR => T_ENDFOR,
109 ],
110 'strict' => false,
111 'shared' => false,
112 'with' => [],
113 ],
114 T_FOREACH => [
115 'start' => [
116 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
117 T_COLON => T_COLON,
118 ],
119 'end' => [
120 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
121 T_ENDFOREACH => T_ENDFOREACH,
122 ],
123 'strict' => false,
124 'shared' => false,
125 'with' => [],
126 ],
127 T_INTERFACE => [
128 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
129 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
130 'strict' => true,
131 'shared' => false,
132 'with' => [],
133 ],
134 T_FUNCTION => [
135 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
136 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
137 'strict' => true,
138 'shared' => false,
139 'with' => [],
140 ],
141 T_CLASS => [
142 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
143 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
144 'strict' => true,
145 'shared' => false,
146 'with' => [],
147 ],
148 T_TRAIT => [
149 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
150 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
151 'strict' => true,
152 'shared' => false,
153 'with' => [],
154 ],
155 T_USE => [
156 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
157 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
158 'strict' => false,
159 'shared' => false,
160 'with' => [],
161 ],
162 T_DECLARE => [
163 'start' => [
164 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
165 T_COLON => T_COLON,
166 ],
167 'end' => [
168 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
169 T_ENDDECLARE => T_ENDDECLARE,
170 ],
171 'strict' => false,
172 'shared' => false,
173 'with' => [],
174 ],
175 T_NAMESPACE => [
176 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
177 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
178 'strict' => false,
179 'shared' => false,
180 'with' => [],
181 ],
182 T_WHILE => [
183 'start' => [
184 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
185 T_COLON => T_COLON,
186 ],
187 'end' => [
188 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
189 T_ENDWHILE => T_ENDWHILE,
190 ],
191 'strict' => false,
192 'shared' => false,
193 'with' => [],
194 ],
195 T_DO => [
196 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
197 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
198 'strict' => true,
199 'shared' => false,
200 'with' => [],
201 ],
202 T_SWITCH => [
203 'start' => [
204 T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
205 T_COLON => T_COLON,
206 ],
207 'end' => [
208 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
209 T_ENDSWITCH => T_ENDSWITCH,
210 ],
211 'strict' => true,
212 'shared' => false,
213 'with' => [],
214 ],
215 T_CASE => [
216 'start' => [
217 T_COLON => T_COLON,
218 T_SEMICOLON => T_SEMICOLON,
219 ],
220 'end' => [
221 T_BREAK => T_BREAK,
222 T_RETURN => T_RETURN,
223 T_CONTINUE => T_CONTINUE,
224 T_THROW => T_THROW,
225 T_EXIT => T_EXIT,
226 ],
227 'strict' => true,
228 'shared' => true,
229 'with' => [
230 T_DEFAULT => T_DEFAULT,
231 T_CASE => T_CASE,
232 T_SWITCH => T_SWITCH,
233 ],
234 ],
235 T_DEFAULT => [
236 'start' => [
237 T_COLON => T_COLON,
238 T_SEMICOLON => T_SEMICOLON,
239 ],
240 'end' => [
241 T_BREAK => T_BREAK,
242 T_RETURN => T_RETURN,
243 T_CONTINUE => T_CONTINUE,
244 T_THROW => T_THROW,
245 T_EXIT => T_EXIT,
246 ],
247 'strict' => true,
248 'shared' => true,
249 'with' => [
250 T_CASE => T_CASE,
251 T_SWITCH => T_SWITCH,
252 ],
253 ],
254 T_START_HEREDOC => [
255 'start' => [T_START_HEREDOC => T_START_HEREDOC],
256 'end' => [T_END_HEREDOC => T_END_HEREDOC],
257 'strict' => true,
258 'shared' => false,
259 'with' => [],
260 ],
261 T_START_NOWDOC => [
262 'start' => [T_START_NOWDOC => T_START_NOWDOC],
263 'end' => [T_END_NOWDOC => T_END_NOWDOC],
264 'strict' => true,
265 'shared' => false,
266 'with' => [],
267 ],
268 ];
269
270 /**
271 * A list of tokens that end the scope.
272 *
273 * This array is just a unique collection of the end tokens
274 * from the scopeOpeners array. The data is duplicated here to
275 * save time during parsing of the file.
276 *
277 * @var array
278 */
279 public $endScopeTokens = [
280 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
281 T_ENDIF => T_ENDIF,
282 T_ENDFOR => T_ENDFOR,
283 T_ENDFOREACH => T_ENDFOREACH,
284 T_ENDWHILE => T_ENDWHILE,
285 T_ENDSWITCH => T_ENDSWITCH,
286 T_BREAK => T_BREAK,
287 T_END_HEREDOC => T_END_HEREDOC,
288 ];
289
290 /**
291 * Known lengths of tokens.
292 *
293 * @var array<int, int>
294 */
295 public $knownLengths = [
296 T_ABSTRACT => 8,
297 T_AND_EQUAL => 2,
298 T_ARRAY => 5,
299 T_AS => 2,
300 T_BOOLEAN_AND => 2,
301 T_BOOLEAN_OR => 2,
302 T_BREAK => 5,
303 T_CALLABLE => 8,
304 T_CASE => 4,
305 T_CATCH => 5,
306 T_CLASS => 5,
307 T_CLASS_C => 9,
308 T_CLONE => 5,
309 T_CONCAT_EQUAL => 2,
310 T_CONST => 5,
311 T_CONTINUE => 8,
312 T_CURLY_OPEN => 2,
313 T_DEC => 2,
314 T_DECLARE => 7,
315 T_DEFAULT => 7,
316 T_DIR => 7,
317 T_DIV_EQUAL => 2,
318 T_DO => 2,
319 T_DOLLAR_OPEN_CURLY_BRACES => 2,
320 T_DOUBLE_ARROW => 2,
321 T_DOUBLE_COLON => 2,
322 T_ECHO => 4,
323 T_ELSE => 4,
324 T_ELSEIF => 6,
325 T_EMPTY => 5,
326 T_ENDDECLARE => 10,
327 T_ENDFOR => 6,
328 T_ENDFOREACH => 10,
329 T_ENDIF => 5,
330 T_ENDSWITCH => 9,
331 T_ENDWHILE => 8,
332 T_EVAL => 4,
333 T_EXTENDS => 7,
334 T_FILE => 8,
335 T_FINAL => 5,
336 T_FINALLY => 7,
337 T_FOR => 3,
338 T_FOREACH => 7,
339 T_FUNCTION => 8,
340 T_FUNC_C => 12,
341 T_GLOBAL => 6,
342 T_GOTO => 4,
343 T_HALT_COMPILER => 15,
344 T_IF => 2,
345 T_IMPLEMENTS => 10,
346 T_INC => 2,
347 T_INCLUDE => 7,
348 T_INCLUDE_ONCE => 12,
349 T_INSTANCEOF => 10,
350 T_INSTEADOF => 9,
351 T_INTERFACE => 9,
352 T_ISSET => 5,
353 T_IS_EQUAL => 2,
354 T_IS_GREATER_OR_EQUAL => 2,
355 T_IS_IDENTICAL => 3,
356 T_IS_NOT_EQUAL => 2,
357 T_IS_NOT_IDENTICAL => 3,
358 T_IS_SMALLER_OR_EQUAL => 2,
359 T_LINE => 8,
360 T_LIST => 4,
361 T_LOGICAL_AND => 3,
362 T_LOGICAL_OR => 2,
363 T_LOGICAL_XOR => 3,
364 T_METHOD_C => 10,
365 T_MINUS_EQUAL => 2,
366 T_POW_EQUAL => 3,
367 T_MOD_EQUAL => 2,
368 T_MUL_EQUAL => 2,
369 T_NAMESPACE => 9,
370 T_NS_C => 13,
371 T_NS_SEPARATOR => 1,
372 T_NEW => 3,
373 T_OBJECT_OPERATOR => 2,
374 T_OPEN_TAG_WITH_ECHO => 3,
375 T_OR_EQUAL => 2,
376 T_PLUS_EQUAL => 2,
377 T_PRINT => 5,
378 T_PRIVATE => 7,
379 T_PUBLIC => 6,
380 T_PROTECTED => 9,
381 T_REQUIRE => 7,
382 T_REQUIRE_ONCE => 12,
383 T_RETURN => 6,
384 T_STATIC => 6,
385 T_SWITCH => 6,
386 T_THROW => 5,
387 T_TRAIT => 5,
388 T_TRAIT_C => 9,
389 T_TRY => 3,
390 T_UNSET => 5,
391 T_USE => 3,
392 T_VAR => 3,
393 T_WHILE => 5,
394 T_XOR_EQUAL => 2,
395 T_YIELD => 5,
396 T_OPEN_CURLY_BRACKET => 1,
397 T_CLOSE_CURLY_BRACKET => 1,
398 T_OPEN_SQUARE_BRACKET => 1,
399 T_CLOSE_SQUARE_BRACKET => 1,
400 T_OPEN_PARENTHESIS => 1,
401 T_CLOSE_PARENTHESIS => 1,
402 T_COLON => 1,
403 T_STRING_CONCAT => 1,
404 T_INLINE_THEN => 1,
405 T_INLINE_ELSE => 1,
406 T_NULLABLE => 1,
407 T_NULL => 4,
408 T_FALSE => 5,
409 T_TRUE => 4,
410 T_SEMICOLON => 1,
411 T_EQUAL => 1,
412 T_MULTIPLY => 1,
413 T_DIVIDE => 1,
414 T_PLUS => 1,
415 T_MINUS => 1,
416 T_MODULUS => 1,
417 T_POW => 2,
418 T_SPACESHIP => 3,
419 T_COALESCE => 2,
420 T_COALESCE_EQUAL => 3,
421 T_BITWISE_AND => 1,
422 T_BITWISE_OR => 1,
423 T_BITWISE_XOR => 1,
424 T_SL => 2,
425 T_SR => 2,
426 T_SL_EQUAL => 3,
427 T_SR_EQUAL => 3,
428 T_GREATER_THAN => 1,
429 T_LESS_THAN => 1,
430 T_BOOLEAN_NOT => 1,
431 T_SELF => 4,
432 T_PARENT => 6,
433 T_COMMA => 1,
434 T_THIS => 4,
435 T_CLOSURE => 8,
436 T_BACKTICK => 1,
437 T_OPEN_SHORT_ARRAY => 1,
438 T_CLOSE_SHORT_ARRAY => 1,
439 ];
440
441 /**
442 * A cache of different token types, resolved into arrays.
443 *
444 * @var array
445 * @see standardiseToken()
446 */
447 private static $resolveTokenCache = [];
448
449
450 /**
451 * Creates an array of tokens when given some PHP code.
452 *
453 * Starts by using token_get_all() but does a lot of extra processing
454 * to insert information about the context of the token.
455 *
456 * @param string $string The string to tokenize.
457 *
458 * @return array
459 */
460 protected function tokenize($string)
461 {
462 if (PHP_CODESNIFFER_VERBOSITY > 1) {
463 echo "\t*** START PHP TOKENIZING ***".PHP_EOL;
464 $isWin = false;
465 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
466 $isWin = true;
467 }
468 }
469
470 $tokens = @token_get_all($string);
471 $finalTokens = [];
472
473 $newStackPtr = 0;
474 $numTokens = count($tokens);
475 $lastNotEmptyToken = 0;
476
477 $insideInlineIf = [];
478 $insideUseGroup = false;
479
480 $commentTokenizer = new Comment();
481
482 for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) {
483 // Special case for tokens we have needed to blank out.
484 if ($tokens[$stackPtr] === null) {
485 continue;
486 }
487
488 $token = (array) $tokens[$stackPtr];
489 $tokenIsArray = isset($token[1]);
490
491 if (PHP_CODESNIFFER_VERBOSITY > 1) {
492 if ($tokenIsArray === true) {
493 $type = Util\Tokens::tokenName($token[0]);
494 $content = Util\Common::prepareForOutput($token[1]);
495 } else {
496 $newToken = self::resolveSimpleToken($token[0]);
497 $type = $newToken['type'];
498 $content = Util\Common::prepareForOutput($token[0]);
499 }
500
501 echo "\tProcess token ";
502 if ($tokenIsArray === true) {
503 echo "[$stackPtr]";
504 } else {
505 echo " $stackPtr ";
506 }
507
508 echo ": $type => $content";
509 }//end if
510
511 if ($newStackPtr > 0
512 && isset(Util\Tokens::$emptyTokens[$finalTokens[($newStackPtr - 1)]['code']]) === false
513 ) {
514 $lastNotEmptyToken = ($newStackPtr - 1);
515 }
516
517 /*
518 If we are using \r\n newline characters, the \r and \n are sometimes
519 split over two tokens. This normally occurs after comments. We need
520 to merge these two characters together so that our line endings are
521 consistent for all lines.
522 */
523
524 if ($tokenIsArray === true && substr($token[1], -1) === "\r") {
525 if (isset($tokens[($stackPtr + 1)]) === true
526 && is_array($tokens[($stackPtr + 1)]) === true
527 && $tokens[($stackPtr + 1)][1][0] === "\n"
528 ) {
529 $token[1] .= "\n";
530 if (PHP_CODESNIFFER_VERBOSITY > 1) {
531 if ($isWin === true) {
532 echo '\n';
533 } else {
534 echo "\033[30;1m\\n\033[0m";
535 }
536 }
537
538 if ($tokens[($stackPtr + 1)][1] === "\n") {
539 // This token's content has been merged into the previous,
540 // so we can skip it.
541 $tokens[($stackPtr + 1)] = '';
542 } else {
543 $tokens[($stackPtr + 1)][1] = substr($tokens[($stackPtr + 1)][1], 1);
544 }
545 }
546 }//end if
547
548 if (PHP_CODESNIFFER_VERBOSITY > 1) {
549 echo PHP_EOL;
550 }
551
552 /*
553 Parse doc blocks into something that can be easily iterated over.
554 */
555
556 if ($tokenIsArray === true
557 && ($token[0] === T_DOC_COMMENT
558 || ($token[0] === T_COMMENT && strpos($token[1], '/**') === 0))
559 ) {
560 $commentTokens = $commentTokenizer->tokenizeString($token[1], $this->eolChar, $newStackPtr);
561 foreach ($commentTokens as $commentToken) {
562 $finalTokens[$newStackPtr] = $commentToken;
563 $newStackPtr++;
564 }
565
566 continue;
567 }
568
569 /*
570 If this is a double quoted string, PHP will tokenize the whole
571 thing which causes problems with the scope map when braces are
572 within the string. So we need to merge the tokens together to
573 provide a single string.
574 */
575
576 if ($tokenIsArray === false && ($token[0] === '"' || $token[0] === 'b"')) {
577 // Binary casts need a special token.
578 if ($token[0] === 'b"') {
579 $finalTokens[$newStackPtr] = [
580 'code' => T_BINARY_CAST,
581 'type' => 'T_BINARY_CAST',
582 'content' => 'b',
583 ];
584 $newStackPtr++;
585 }
586
587 $tokenContent = '"';
588 $nestedVars = [];
589 for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
590 $subToken = (array) $tokens[$i];
591 $subTokenIsArray = isset($subToken[1]);
592
593 if ($subTokenIsArray === true) {
594 $tokenContent .= $subToken[1];
595 if ($subToken[1] === '{'
596 && $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
597 ) {
598 $nestedVars[] = $i;
599 }
600 } else {
601 $tokenContent .= $subToken[0];
602 if ($subToken[0] === '}') {
603 array_pop($nestedVars);
604 }
605 }
606
607 if ($subTokenIsArray === false
608 && $subToken[0] === '"'
609 && empty($nestedVars) === true
610 ) {
611 // We found the other end of the double quoted string.
612 break;
613 }
614 }//end for
615
616 $stackPtr = $i;
617
618 // Convert each line within the double quoted string to a
619 // new token, so it conforms with other multiple line tokens.
620 $tokenLines = explode($this->eolChar, $tokenContent);
621 $numLines = count($tokenLines);
622 $newToken = [];
623
624 for ($j = 0; $j < $numLines; $j++) {
625 $newToken['content'] = $tokenLines[$j];
626 if ($j === ($numLines - 1)) {
627 if ($tokenLines[$j] === '') {
628 break;
629 }
630 } else {
631 $newToken['content'] .= $this->eolChar;
632 }
633
634 $newToken['code'] = T_DOUBLE_QUOTED_STRING;
635 $newToken['type'] = 'T_DOUBLE_QUOTED_STRING';
636 $finalTokens[$newStackPtr] = $newToken;
637 $newStackPtr++;
638 }
639
640 // Continue, as we're done with this token.
641 continue;
642 }//end if
643
644 /*
645 Detect binary casting and assign the casts their own token.
646 */
647
648 if ($tokenIsArray === true
649 && $token[0] === T_CONSTANT_ENCAPSED_STRING
650 && (substr($token[1], 0, 2) === 'b"'
651 || substr($token[1], 0, 2) === "b'")
652 ) {
653 $finalTokens[$newStackPtr] = [
654 'code' => T_BINARY_CAST,
655 'type' => 'T_BINARY_CAST',
656 'content' => 'b',
657 ];
658 $newStackPtr++;
659 $token[1] = substr($token[1], 1);
660 }
661
662 if ($tokenIsArray === true
663 && $token[0] === T_STRING_CAST
664 && preg_match('`^\(\s*binary\s*\)$`i', $token[1]) === 1
665 ) {
666 $finalTokens[$newStackPtr] = [
667 'code' => T_BINARY_CAST,
668 'type' => 'T_BINARY_CAST',
669 'content' => $token[1],
670 ];
671 $newStackPtr++;
672 continue;
673 }
674
675 /*
676 If this is a heredoc, PHP will tokenize the whole
677 thing which causes problems when heredocs don't
678 contain real PHP code, which is almost never.
679 We want to leave the start and end heredoc tokens
680 alone though.
681 */
682
683 if ($tokenIsArray === true && $token[0] === T_START_HEREDOC) {
684 // Add the start heredoc token to the final array.
685 $finalTokens[$newStackPtr] = self::standardiseToken($token);
686
687 // Check if this is actually a nowdoc and use a different token
688 // to help the sniffs.
689 $nowdoc = false;
690 if (strpos($token[1], "'") !== false) {
691 $finalTokens[$newStackPtr]['code'] = T_START_NOWDOC;
692 $finalTokens[$newStackPtr]['type'] = 'T_START_NOWDOC';
693 $nowdoc = true;
694 }
695
696 $tokenContent = '';
697 for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
698 $subTokenIsArray = is_array($tokens[$i]);
699 if ($subTokenIsArray === true
700 && $tokens[$i][0] === T_END_HEREDOC
701 ) {
702 // We found the other end of the heredoc.
703 break;
704 }
705
706 if ($subTokenIsArray === true) {
707 $tokenContent .= $tokens[$i][1];
708 } else {
709 $tokenContent .= $tokens[$i];
710 }
711 }
712
713 if ($i === $numTokens) {
714 // We got to the end of the file and never
715 // found the closing token, so this probably wasn't
716 // a heredoc.
717 if (PHP_CODESNIFFER_VERBOSITY > 1) {
718 $type = $finalTokens[$newStackPtr]['type'];
719 echo "\t\t* failed to find the end of the here/nowdoc".PHP_EOL;
720 echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
721 }
722
723 $finalTokens[$newStackPtr]['code'] = T_STRING;
724 $finalTokens[$newStackPtr]['type'] = 'T_STRING';
725 $newStackPtr++;
726 continue;
727 }
728
729 $stackPtr = $i;
730 $newStackPtr++;
731
732 // Convert each line within the heredoc to a
733 // new token, so it conforms with other multiple line tokens.
734 $tokenLines = explode($this->eolChar, $tokenContent);
735 $numLines = count($tokenLines);
736 $newToken = [];
737
738 for ($j = 0; $j < $numLines; $j++) {
739 $newToken['content'] = $tokenLines[$j];
740 if ($j === ($numLines - 1)) {
741 if ($tokenLines[$j] === '') {
742 break;
743 }
744 } else {
745 $newToken['content'] .= $this->eolChar;
746 }
747
748 if ($nowdoc === true) {
749 $newToken['code'] = T_NOWDOC;
750 $newToken['type'] = 'T_NOWDOC';
751 } else {
752 $newToken['code'] = T_HEREDOC;
753 $newToken['type'] = 'T_HEREDOC';
754 }
755
756 $finalTokens[$newStackPtr] = $newToken;
757 $newStackPtr++;
758 }//end for
759
760 // Add the end heredoc token to the final array.
761 $finalTokens[$newStackPtr] = self::standardiseToken($tokens[$stackPtr]);
762
763 if ($nowdoc === true) {
764 $finalTokens[$newStackPtr]['code'] = T_END_NOWDOC;
765 $finalTokens[$newStackPtr]['type'] = 'T_END_NOWDOC';
766 }
767
768 $newStackPtr++;
769
770 // Continue, as we're done with this token.
771 continue;
772 }//end if
773
774 /*
775 Before PHP 7.0, the "yield from" was tokenized as
776 T_YIELD, T_WHITESPACE and T_STRING. So look for
777 and change this token in earlier versions.
778 */
779
780 if (PHP_VERSION_ID < 70000
781 && PHP_VERSION_ID >= 50500
782 && $tokenIsArray === true
783 && $token[0] === T_YIELD
784 && isset($tokens[($stackPtr + 1)]) === true
785 && isset($tokens[($stackPtr + 2)]) === true
786 && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
787 && $tokens[($stackPtr + 2)][0] === T_STRING
788 && strtolower($tokens[($stackPtr + 2)][1]) === 'from'
789 ) {
790 // Could be multi-line, so just the token stack.
791 $token[0] = T_YIELD_FROM;
792 $token[1] .= $tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1];
793
794 if (PHP_CODESNIFFER_VERBOSITY > 1) {
795 for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) {
796 $type = Util\Tokens::tokenName($tokens[$i][0]);
797 $content = Util\Common::prepareForOutput($tokens[$i][1]);
798 echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL;
799 }
800 }
801
802 $tokens[($stackPtr + 1)] = null;
803 $tokens[($stackPtr + 2)] = null;
804 }
805
806 /*
807 Before PHP 5.5, the yield keyword was tokenized as
808 T_STRING. So look for and change this token in
809 earlier versions.
810 Checks also if it is just "yield" or "yield from".
811 */
812
813 if (PHP_VERSION_ID < 50500
814 && $tokenIsArray === true
815 && $token[0] === T_STRING
816 && strtolower($token[1]) === 'yield'
817 ) {
818 if (isset($tokens[($stackPtr + 1)]) === true
819 && isset($tokens[($stackPtr + 2)]) === true
820 && $tokens[($stackPtr + 1)][0] === T_WHITESPACE
821 && $tokens[($stackPtr + 2)][0] === T_STRING
822 && strtolower($tokens[($stackPtr + 2)][1]) === 'from'
823 ) {
824 // Could be multi-line, so just just the token stack.
825 $token[0] = T_YIELD_FROM;
826 $token[1] .= $tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1];
827
828 if (PHP_CODESNIFFER_VERBOSITY > 1) {
829 for ($i = ($stackPtr + 1); $i <= ($stackPtr + 2); $i++) {
830 $type = Util\Tokens::tokenName($tokens[$i][0]);
831 $content = Util\Common::prepareForOutput($tokens[$i][1]);
832 echo "\t\t* token $i merged into T_YIELD_FROM; was: $type => $content".PHP_EOL;
833 }
834 }
835
836 $tokens[($stackPtr + 1)] = null;
837 $tokens[($stackPtr + 2)] = null;
838 } else {
839 $newToken = [];
840 $newToken['code'] = T_YIELD;
841 $newToken['type'] = 'T_YIELD';
842 $newToken['content'] = $token[1];
843 $finalTokens[$newStackPtr] = $newToken;
844
845 $newStackPtr++;
846 continue;
847 }//end if
848 }//end if
849
850 /*
851 Before PHP 5.6, the ... operator was tokenized as three
852 T_STRING_CONCAT tokens in a row. So look for and combine
853 these tokens in earlier versions.
854 */
855
856 if ($tokenIsArray === false
857 && $token[0] === '.'
858 && isset($tokens[($stackPtr + 1)]) === true
859 && isset($tokens[($stackPtr + 2)]) === true
860 && $tokens[($stackPtr + 1)] === '.'
861 && $tokens[($stackPtr + 2)] === '.'
862 ) {
863 $newToken = [];
864 $newToken['code'] = T_ELLIPSIS;
865 $newToken['type'] = 'T_ELLIPSIS';
866 $newToken['content'] = '...';
867 $finalTokens[$newStackPtr] = $newToken;
868
869 $newStackPtr++;
870 $stackPtr += 2;
871 continue;
872 }
873
874 /*
875 Before PHP 5.6, the ** operator was tokenized as two
876 T_MULTIPLY tokens in a row. So look for and combine
877 these tokens in earlier versions.
878 */
879
880 if ($tokenIsArray === false
881 && $token[0] === '*'
882 && isset($tokens[($stackPtr + 1)]) === true
883 && $tokens[($stackPtr + 1)] === '*'
884 ) {
885 $newToken = [];
886 $newToken['code'] = T_POW;
887 $newToken['type'] = 'T_POW';
888 $newToken['content'] = '**';
889 $finalTokens[$newStackPtr] = $newToken;
890
891 $newStackPtr++;
892 $stackPtr++;
893 continue;
894 }
895
896 /*
897 Before PHP 5.6, the **= operator was tokenized as
898 T_MULTIPLY followed by T_MUL_EQUAL. So look for and combine
899 these tokens in earlier versions.
900 */
901
902 if ($tokenIsArray === false
903 && $token[0] === '*'
904 && isset($tokens[($stackPtr + 1)]) === true
905 && is_array($tokens[($stackPtr + 1)]) === true
906 && $tokens[($stackPtr + 1)][1] === '*='
907 ) {
908 $newToken = [];
909 $newToken['code'] = T_POW_EQUAL;
910 $newToken['type'] = 'T_POW_EQUAL';
911 $newToken['content'] = '**=';
912 $finalTokens[$newStackPtr] = $newToken;
913
914 $newStackPtr++;
915 $stackPtr++;
916 continue;
917 }
918
919 /*
920 Before PHP 7, the ??= operator was tokenized as
921 T_INLINE_THEN, T_INLINE_THEN, T_EQUAL.
922 Between PHP 7.0 and 7.2, the ??= operator was tokenized as
923 T_COALESCE, T_EQUAL.
924 So look for and combine these tokens in earlier versions.
925 */
926
927 if (($tokenIsArray === false
928 && $token[0] === '?'
929 && isset($tokens[($stackPtr + 1)]) === true
930 && $tokens[($stackPtr + 1)][0] === '?'
931 && isset($tokens[($stackPtr + 2)]) === true
932 && $tokens[($stackPtr + 2)][0] === '=')
933 || ($tokenIsArray === true
934 && $token[0] === T_COALESCE
935 && isset($tokens[($stackPtr + 1)]) === true
936 && $tokens[($stackPtr + 1)][0] === '=')
937 ) {
938 $newToken = [];
939 $newToken['code'] = T_COALESCE_EQUAL;
940 $newToken['type'] = 'T_COALESCE_EQUAL';
941 $newToken['content'] = '??=';
942 $finalTokens[$newStackPtr] = $newToken;
943
944 $newStackPtr++;
945 $stackPtr++;
946
947 if ($tokenIsArray === false) {
948 // Pre PHP 7.
949 $stackPtr++;
950 }
951
952 continue;
953 }
954
955 /*
956 Before PHP 7, the ?? operator was tokenized as
957 T_INLINE_THEN followed by T_INLINE_THEN.
958 So look for and combine these tokens in earlier versions.
959 */
960
961 if ($tokenIsArray === false
962 && $token[0] === '?'
963 && isset($tokens[($stackPtr + 1)]) === true
964 && $tokens[($stackPtr + 1)][0] === '?'
965 ) {
966 $newToken = [];
967 $newToken['code'] = T_COALESCE;
968 $newToken['type'] = 'T_COALESCE';
969 $newToken['content'] = '??';
970 $finalTokens[$newStackPtr] = $newToken;
971
972 $newStackPtr++;
973 $stackPtr++;
974 continue;
975 }
976
977 /*
978 Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER
979 tokens split the token with a T_STRING. So look for
980 and change these tokens in earlier versions.
981 */
982
983 if (PHP_VERSION_ID < 70400
984 && ($tokenIsArray === true
985 && ($token[0] === T_LNUMBER
986 || $token[0] === T_DNUMBER)
987 && isset($tokens[($stackPtr + 1)]) === true
988 && is_array($tokens[($stackPtr + 1)]) === true
989 && $tokens[($stackPtr + 1)][0] === T_STRING
990 && $tokens[($stackPtr + 1)][1][0] === '_')
991 ) {
992 $newContent = $token[1];
993 $newType = $token[0];
994 for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
995 if (is_array($tokens[$i]) === false) {
996 break;
997 }
998
999 if ($tokens[$i][0] === T_LNUMBER
1000 || $tokens[$i][0] === T_DNUMBER
1001 ) {
1002 $newContent .= $tokens[$i][1];
1003 continue;
1004 }
1005
1006 if ($tokens[$i][0] === T_STRING
1007 && $tokens[$i][1][0] === '_'
1008 && ((strpos($newContent, '0x') === 0
1009 && preg_match('`^((?<!\.)_[0-9A-F][0-9A-F\.]*)+$`iD', $tokens[$i][1]) === 1)
1010 || (strpos($newContent, '0x') !== 0
1011 && substr($newContent, -1) !== '.'
1012 && substr(strtolower($newContent), -1) !== 'e'
1013 && preg_match('`^(?:(?<![\.e])_[0-9][0-9e\.]*)+$`iD', $tokens[$i][1]) === 1))
1014 ) {
1015 $newContent .= $tokens[$i][1];
1016
1017 // Support floats.
1018 if (substr(strtolower($tokens[$i][1]), -1) === 'e'
1019 && ($tokens[($i + 1)] === '-'
1020 || $tokens[($i + 1)] === '+')
1021 ) {
1022 $newContent .= $tokens[($i + 1)];
1023 $i++;
1024 }
1025
1026 continue;
1027 }//end if
1028
1029 break;
1030 }//end for
1031
1032 if ($newType === T_LNUMBER
1033 && ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
1034 || (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
1035 || (stripos($newContent, '0x') !== 0
1036 && stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false)
1037 || (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
1038 && stripos($newContent, '0b') !== 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
1039 || (strpos($newContent, '0') !== 0 && str_replace('_', '', $newContent) > PHP_INT_MAX))
1040 ) {
1041 $newType = T_DNUMBER;
1042 }
1043
1044 $newToken = [];
1045 $newToken['code'] = $newType;
1046 $newToken['type'] = Util\Tokens::tokenName($newType);
1047 $newToken['content'] = $newContent;
1048 $finalTokens[$newStackPtr] = $newToken;
1049
1050 $newStackPtr++;
1051 $stackPtr = ($i - 1);
1052 continue;
1053 }//end if
1054
1055 /*
1056 Convert ? to T_NULLABLE OR T_INLINE_THEN
1057 */
1058
1059 if ($tokenIsArray === false && $token[0] === '?') {
1060 $newToken = [];
1061 $newToken['content'] = '?';
1062
1063 /*
1064 * Check if the next non-empty token is one of the tokens which can be used
1065 * in type declarations. If not, it's definitely a ternary.
1066 * At this point, the only token types which need to be taken into consideration
1067 * as potential type declarations are T_STRING, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
1068 */
1069
1070 $lastRelevantNonEmpty = null;
1071
1072 for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1073 if (is_array($tokens[$i]) === true) {
1074 $tokenType = $tokens[$i][0];
1075 } else {
1076 $tokenType = $tokens[$i];
1077 }
1078
1079 if (isset(Util\Tokens::$emptyTokens[$tokenType]) === true) {
1080 continue;
1081 }
1082
1083 if ($tokenType === T_STRING
1084 || $tokenType === T_ARRAY
1085 || $tokenType === T_NS_SEPARATOR
1086 ) {
1087 $lastRelevantNonEmpty = $tokenType;
1088 continue;
1089 }
1090
1091 if (($tokenType !== T_CALLABLE
1092 && isset($lastRelevantNonEmpty) === false)
1093 || ($lastRelevantNonEmpty === T_ARRAY
1094 && $tokenType === '(')
1095 || ($lastRelevantNonEmpty === T_STRING
1096 && ($tokenType === T_DOUBLE_COLON
1097 || $tokenType === '('
1098 || $tokenType === ':'))
1099 ) {
1100 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1101 echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
1102 }
1103
1104 $newToken['code'] = T_INLINE_THEN;
1105 $newToken['type'] = 'T_INLINE_THEN';
1106
1107 $insideInlineIf[] = $stackPtr;
1108
1109 $finalTokens[$newStackPtr] = $newToken;
1110 $newStackPtr++;
1111 continue 2;
1112 }
1113
1114 break;
1115 }//end for
1116
1117 /*
1118 * This can still be a nullable type or a ternary.
1119 * Do additional checking.
1120 */
1121
1122 $prevNonEmpty = null;
1123 $lastSeenNonEmpty = null;
1124
1125 for ($i = ($stackPtr - 1); $i >= 0; $i--) {
1126 if (is_array($tokens[$i]) === true) {
1127 $tokenType = $tokens[$i][0];
1128 } else {
1129 $tokenType = $tokens[$i];
1130 }
1131
1132 if ($tokenType === T_STATIC
1133 && ($lastSeenNonEmpty === T_DOUBLE_COLON
1134 || $lastSeenNonEmpty === '(')
1135 ) {
1136 $lastSeenNonEmpty = $tokenType;
1137 continue;
1138 }
1139
1140 if ($prevNonEmpty === null
1141 && isset(Util\Tokens::$emptyTokens[$tokenType]) === false
1142 ) {
1143 // Found the previous non-empty token.
1144 if ($tokenType === ':' || $tokenType === ',') {
1145 $newToken['code'] = T_NULLABLE;
1146 $newToken['type'] = 'T_NULLABLE';
1147
1148 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1149 echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
1150 }
1151
1152 break;
1153 }
1154
1155 $prevNonEmpty = $tokenType;
1156 }
1157
1158 if ($tokenType === T_FUNCTION
1159 || $tokenType === T_FN
1160 || isset(Util\Tokens::$methodPrefixes[$tokenType]) === true
1161 || $tokenType === T_VAR
1162 ) {
1163 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1164 echo "\t\t* token $stackPtr changed from ? to T_NULLABLE".PHP_EOL;
1165 }
1166
1167 $newToken['code'] = T_NULLABLE;
1168 $newToken['type'] = 'T_NULLABLE';
1169 break;
1170 } else if (in_array($tokenType, [T_DOUBLE_ARROW, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, '=', '{', ';'], true) === true) {
1171 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1172 echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
1173 }
1174
1175 $newToken['code'] = T_INLINE_THEN;
1176 $newToken['type'] = 'T_INLINE_THEN';
1177
1178 $insideInlineIf[] = $stackPtr;
1179 break;
1180 }
1181
1182 if (isset(Util\Tokens::$emptyTokens[$tokenType]) === false) {
1183 $lastSeenNonEmpty = $tokenType;
1184 }
1185 }//end for
1186
1187 $finalTokens[$newStackPtr] = $newToken;
1188 $newStackPtr++;
1189 continue;
1190 }//end if
1191
1192 /*
1193 Tokens after a double colon may look like scope openers,
1194 such as when writing code like Foo::NAMESPACE, but they are
1195 only ever variables or strings.
1196 */
1197
1198 if ($stackPtr > 1
1199 && (is_array($tokens[($stackPtr - 1)]) === true
1200 && $tokens[($stackPtr - 1)][0] === T_PAAMAYIM_NEKUDOTAYIM)
1201 && $tokenIsArray === true
1202 && $token[0] !== T_STRING
1203 && $token[0] !== T_VARIABLE
1204 && $token[0] !== T_DOLLAR
1205 && isset(Util\Tokens::$emptyTokens[$token[0]]) === false
1206 ) {
1207 $newToken = [];
1208 $newToken['code'] = T_STRING;
1209 $newToken['type'] = 'T_STRING';
1210 $newToken['content'] = $token[1];
1211 $finalTokens[$newStackPtr] = $newToken;
1212
1213 $newStackPtr++;
1214 continue;
1215 }
1216
1217 /*
1218 Backfill the T_FN token for PHP versions < 7.4.
1219 */
1220
1221 if ($tokenIsArray === true
1222 && $token[0] === T_STRING
1223 && strtolower($token[1]) === 'fn'
1224 ) {
1225 // Modify the original token stack so that
1226 // future checks (like looking for T_NULLABLE) can
1227 // detect the T_FN token more easily.
1228 $tokens[$stackPtr][0] = T_FN;
1229 $token[0] = T_FN;
1230 }
1231
1232 /*
1233 The string-like token after a function keyword should always be
1234 tokenized as T_STRING even if it appears to be a different token,
1235 such as when writing code like: function default(): foo
1236 so go forward and change the token type before it is processed.
1237 */
1238
1239 if ($tokenIsArray === true
1240 && ($token[0] === T_FUNCTION
1241 || $token[0] === T_FN)
1242 && $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
1243 ) {
1244 for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1245 if (is_array($tokens[$x]) === false
1246 || isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false
1247 ) {
1248 // Non-empty content.
1249 break;
1250 }
1251 }
1252
1253 if ($x < $numTokens && is_array($tokens[$x]) === true) {
1254 $tokens[$x][0] = T_STRING;
1255 }
1256
1257 /*
1258 This is a special condition for T_ARRAY tokens used for
1259 function return types. We want to keep the parenthesis map clean,
1260 so let's tag these tokens as T_STRING.
1261 */
1262
1263 // Go looking for the colon to start the return type hint.
1264 // Start by finding the closing parenthesis of the function.
1265 $parenthesisStack = [];
1266 $parenthesisCloser = false;
1267 for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1268 if (is_array($tokens[$x]) === false && $tokens[$x] === '(') {
1269 $parenthesisStack[] = $x;
1270 } else if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
1271 array_pop($parenthesisStack);
1272 if (empty($parenthesisStack) === true) {
1273 $parenthesisCloser = $x;
1274 break;
1275 }
1276 }
1277 }
1278
1279 if ($parenthesisCloser !== false) {
1280 for ($x = ($parenthesisCloser + 1); $x < $numTokens; $x++) {
1281 if (is_array($tokens[$x]) === false
1282 || isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false
1283 ) {
1284 // Non-empty content.
1285 if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_USE) {
1286 // Found a use statements, so search ahead for the closing parenthesis.
1287 for ($x += 1; $x < $numTokens; $x++) {
1288 if (is_array($tokens[$x]) === false && $tokens[$x] === ')') {
1289 continue(2);
1290 }
1291 }
1292 }
1293
1294 break;
1295 }
1296 }
1297
1298 if (isset($tokens[$x]) === true
1299 && is_array($tokens[$x]) === false
1300 && $tokens[$x] === ':'
1301 ) {
1302 $allowed = [
1303 T_STRING => T_STRING,
1304 T_ARRAY => T_ARRAY,
1305 T_CALLABLE => T_CALLABLE,
1306 T_SELF => T_SELF,
1307 T_PARENT => T_PARENT,
1308 T_NS_SEPARATOR => T_NS_SEPARATOR,
1309 ];
1310
1311 $allowed += Util\Tokens::$emptyTokens;
1312
1313 // Find the start of the return type.
1314 for ($x += 1; $x < $numTokens; $x++) {
1315 if (is_array($tokens[$x]) === true
1316 && isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === true
1317 ) {
1318 // Whitespace or comments before the return type.
1319 continue;
1320 }
1321
1322 if (is_array($tokens[$x]) === false && $tokens[$x] === '?') {
1323 // Found a nullable operator, so skip it.
1324 // But also covert the token to save the tokenizer
1325 // a bit of time later on.
1326 $tokens[$x] = [
1327 T_NULLABLE,
1328 '?',
1329 ];
1330
1331 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1332 echo "\t\t* token $x changed from ? to T_NULLABLE".PHP_EOL;
1333 }
1334
1335 continue;
1336 }
1337
1338 break;
1339 }//end for
1340
1341 // Any T_ARRAY tokens we find between here and the next
1342 // token that can't be part of the return type need to be
1343 // converted to T_STRING tokens.
1344 for ($x; $x < $numTokens; $x++) {
1345 if (is_array($tokens[$x]) === false || isset($allowed[$tokens[$x][0]]) === false) {
1346 break;
1347 } else if ($tokens[$x][0] === T_ARRAY) {
1348 $tokens[$x][0] = T_STRING;
1349
1350 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1351 echo "\t\t* token $x changed from T_ARRAY to T_STRING".PHP_EOL;
1352 }
1353 }
1354 }
1355 }//end if
1356 }//end if
1357 }//end if
1358
1359 /*
1360 Before PHP 7, the <=> operator was tokenized as
1361 T_IS_SMALLER_OR_EQUAL followed by T_GREATER_THAN.
1362 So look for and combine these tokens in earlier versions.
1363 */
1364
1365 if ($tokenIsArray === true
1366 && $token[0] === T_IS_SMALLER_OR_EQUAL
1367 && isset($tokens[($stackPtr + 1)]) === true
1368 && $tokens[($stackPtr + 1)][0] === '>'
1369 ) {
1370 $newToken = [];
1371 $newToken['code'] = T_SPACESHIP;
1372 $newToken['type'] = 'T_SPACESHIP';
1373 $newToken['content'] = '<=>';
1374 $finalTokens[$newStackPtr] = $newToken;
1375
1376 $newStackPtr++;
1377 $stackPtr++;
1378 continue;
1379 }
1380
1381 /*
1382 PHP doesn't assign a token to goto labels, so we have to.
1383 These are just string tokens with a single colon after them. Double
1384 colons are already tokenized and so don't interfere with this check.
1385 But we do have to account for CASE statements, that look just like
1386 goto labels.
1387 */
1388
1389 if ($tokenIsArray === true
1390 && $token[0] === T_STRING
1391 && isset($tokens[($stackPtr + 1)]) === true
1392 && $tokens[($stackPtr + 1)] === ':'
1393 && $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM
1394 ) {
1395 $stopTokens = [
1396 T_CASE => true,
1397 T_SEMICOLON => true,
1398 T_OPEN_CURLY_BRACKET => true,
1399 T_INLINE_THEN => true,
1400 ];
1401
1402 for ($x = ($newStackPtr - 1); $x > 0; $x--) {
1403 if (isset($stopTokens[$finalTokens[$x]['code']]) === true) {
1404 break;
1405 }
1406 }
1407
1408 if ($finalTokens[$x]['code'] !== T_CASE
1409 && $finalTokens[$x]['code'] !== T_INLINE_THEN
1410 ) {
1411 $finalTokens[$newStackPtr] = [
1412 'content' => $token[1].':',
1413 'code' => T_GOTO_LABEL,
1414 'type' => 'T_GOTO_LABEL',
1415 ];
1416
1417 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1418 echo "\t\t* token $stackPtr changed from T_STRING to T_GOTO_LABEL".PHP_EOL;
1419 echo "\t\t* skipping T_COLON token ".($stackPtr + 1).PHP_EOL;
1420 }
1421
1422 $newStackPtr++;
1423 $stackPtr++;
1424 continue;
1425 }
1426 }//end if
1427
1428 /*
1429 If this token has newlines in its content, split each line up
1430 and create a new token for each line. We do this so it's easier
1431 to ascertain where errors occur on a line.
1432 Note that $token[1] is the token's content.
1433 */
1434
1435 if ($tokenIsArray === true && strpos($token[1], $this->eolChar) !== false) {
1436 $tokenLines = explode($this->eolChar, $token[1]);
1437 $numLines = count($tokenLines);
1438 $newToken = [
1439 'type' => Util\Tokens::tokenName($token[0]),
1440 'code' => $token[0],
1441 'content' => '',
1442 ];
1443
1444 for ($i = 0; $i < $numLines; $i++) {
1445 $newToken['content'] = $tokenLines[$i];
1446 if ($i === ($numLines - 1)) {
1447 if ($tokenLines[$i] === '') {
1448 break;
1449 }
1450 } else {
1451 $newToken['content'] .= $this->eolChar;
1452 }
1453
1454 $finalTokens[$newStackPtr] = $newToken;
1455 $newStackPtr++;
1456 }
1457 } else {
1458 if ($tokenIsArray === true && $token[0] === T_STRING) {
1459 // Some T_STRING tokens should remain that way
1460 // due to their context.
1461 $context = [
1462 T_OBJECT_OPERATOR => true,
1463 T_FUNCTION => true,
1464 T_CLASS => true,
1465 T_EXTENDS => true,
1466 T_IMPLEMENTS => true,
1467 T_NEW => true,
1468 T_CONST => true,
1469 T_NS_SEPARATOR => true,
1470 T_USE => true,
1471 T_NAMESPACE => true,
1472 T_PAAMAYIM_NEKUDOTAYIM => true,
1473 ];
1474
1475 if (isset($context[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
1476 // Special case for syntax like: return new self
1477 // where self should not be a string.
1478 if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
1479 && strtolower($token[1]) === 'self'
1480 ) {
1481 $finalTokens[$newStackPtr] = [
1482 'content' => $token[1],
1483 'code' => T_SELF,
1484 'type' => 'T_SELF',
1485 ];
1486 } else {
1487 $finalTokens[$newStackPtr] = [
1488 'content' => $token[1],
1489 'code' => T_STRING,
1490 'type' => 'T_STRING',
1491 ];
1492 }
1493
1494 $newStackPtr++;
1495 continue;
1496 }//end if
1497 }//end if
1498
1499 $newToken = null;
1500 if ($tokenIsArray === false) {
1501 if (isset(self::$resolveTokenCache[$token[0]]) === true) {
1502 $newToken = self::$resolveTokenCache[$token[0]];
1503 }
1504 } else {
1505 $cacheKey = null;
1506 if ($token[0] === T_STRING) {
1507 $cacheKey = strtolower($token[1]);
1508 } else if ($token[0] !== T_CURLY_OPEN) {
1509 $cacheKey = $token[0];
1510 }
1511
1512 if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
1513 $newToken = self::$resolveTokenCache[$cacheKey];
1514 $newToken['content'] = $token[1];
1515 }
1516 }
1517
1518 if ($newToken === null) {
1519 $newToken = self::standardiseToken($token);
1520 }
1521
1522 // Convert colons that are actually the ELSE component of an
1523 // inline IF statement.
1524 if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) {
1525 // Make sure this isn't the return type separator of a closure.
1526 $isInlineIf = true;
1527 for ($i = ($stackPtr - 1); $i > 0; $i--) {
1528 if (is_array($tokens[$i]) === false
1529 || ($tokens[$i][0] !== T_DOC_COMMENT
1530 && $tokens[$i][0] !== T_COMMENT
1531 && $tokens[$i][0] !== T_WHITESPACE)
1532 ) {
1533 break;
1534 }
1535 }
1536
1537 if ($tokens[$i] === ')') {
1538 $parenCount = 1;
1539 for ($i--; $i > 0; $i--) {
1540 if ($tokens[$i] === '(') {
1541 $parenCount--;
1542 if ($parenCount === 0) {
1543 break;
1544 }
1545 } else if ($tokens[$i] === ')') {
1546 $parenCount++;
1547 }
1548 }
1549
1550 // We've found the open parenthesis, so if the previous
1551 // non-empty token is FUNCTION or USE, this is a closure.
1552 for ($i--; $i > 0; $i--) {
1553 if (is_array($tokens[$i]) === false
1554 || ($tokens[$i][0] !== T_DOC_COMMENT
1555 && $tokens[$i][0] !== T_COMMENT
1556 && $tokens[$i][0] !== T_WHITESPACE)
1557 ) {
1558 break;
1559 }
1560 }
1561
1562 if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) {
1563 $isInlineIf = false;
1564 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1565 echo "\t\t* token is function return type, not T_INLINE_ELSE".PHP_EOL;
1566 }
1567 }
1568 }//end if
1569
1570 // Check to see if this is a CASE or DEFAULT opener.
1571 $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)];
1572 for ($i = $stackPtr; $i > $inlineIfToken; $i--) {
1573 if (is_array($tokens[$i]) === true
1574 && ($tokens[$i][0] === T_CASE
1575 || $tokens[$i][0] === T_DEFAULT)
1576 ) {
1577 $isInlineIf = false;
1578 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1579 echo "\t\t* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE".PHP_EOL;
1580 }
1581
1582 break;
1583 }
1584
1585 if (is_array($tokens[$i]) === false
1586 && ($tokens[$i] === ';'
1587 || $tokens[$i] === '{')
1588 ) {
1589 break;
1590 }
1591 }
1592
1593 if ($isInlineIf === true) {
1594 array_pop($insideInlineIf);
1595 $newToken['code'] = T_INLINE_ELSE;
1596 $newToken['type'] = 'T_INLINE_ELSE';
1597
1598 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1599 echo "\t\t* token changed from T_COLON to T_INLINE_ELSE".PHP_EOL;
1600 }
1601 }
1602 }//end if
1603
1604 // This is a special condition for T_ARRAY tokens used for
1605 // type hinting function arguments as being arrays. We want to keep
1606 // the parenthesis map clean, so let's tag these tokens as
1607 // T_STRING.
1608 if ($newToken['code'] === T_ARRAY) {
1609 for ($i = $stackPtr; $i < $numTokens; $i++) {
1610 if ($tokens[$i] === '(') {
1611 break;
1612 } else if ($tokens[$i][0] === T_VARIABLE) {
1613 $newToken['code'] = T_STRING;
1614 $newToken['type'] = 'T_STRING';
1615 break;
1616 }
1617 }
1618 }
1619
1620 // This is a special case when checking PHP 5.5+ code in PHP < 5.5
1621 // where "finally" should be T_FINALLY instead of T_STRING.
1622 if ($newToken['code'] === T_STRING
1623 && strtolower($newToken['content']) === 'finally'
1624 ) {
1625 $newToken['code'] = T_FINALLY;
1626 $newToken['type'] = 'T_FINALLY';
1627 }
1628
1629 // This is a special case for the PHP 5.5 classname::class syntax
1630 // where "class" should be T_STRING instead of T_CLASS.
1631 if (($newToken['code'] === T_CLASS
1632 || $newToken['code'] === T_FUNCTION)
1633 && $finalTokens[$lastNotEmptyToken]['code'] === T_DOUBLE_COLON
1634 ) {
1635 $newToken['code'] = T_STRING;
1636 $newToken['type'] = 'T_STRING';
1637 }
1638
1639 // This is a special case for PHP 5.6 use function and use const
1640 // where "function" and "const" should be T_STRING instead of T_FUNCTION
1641 // and T_CONST.
1642 if (($newToken['code'] === T_FUNCTION
1643 || $newToken['code'] === T_CONST)
1644 && ($finalTokens[$lastNotEmptyToken]['code'] === T_USE || $insideUseGroup === true)
1645 ) {
1646 $newToken['code'] = T_STRING;
1647 $newToken['type'] = 'T_STRING';
1648 }
1649
1650 // This is a special case for use groups in PHP 7+ where leaving
1651 // the curly braces as their normal tokens would confuse
1652 // the scope map and sniffs.
1653 if ($newToken['code'] === T_OPEN_CURLY_BRACKET
1654 && $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
1655 ) {
1656 $newToken['code'] = T_OPEN_USE_GROUP;
1657 $newToken['type'] = 'T_OPEN_USE_GROUP';
1658 $insideUseGroup = true;
1659 }
1660
1661 if ($insideUseGroup === true && $newToken['code'] === T_CLOSE_CURLY_BRACKET) {
1662 $newToken['code'] = T_CLOSE_USE_GROUP;
1663 $newToken['type'] = 'T_CLOSE_USE_GROUP';
1664 $insideUseGroup = false;
1665 }
1666
1667 $finalTokens[$newStackPtr] = $newToken;
1668 $newStackPtr++;
1669 }//end if
1670 }//end for
1671
1672 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1673 echo "\t*** END PHP TOKENIZING ***".PHP_EOL;
1674 }
1675
1676 return $finalTokens;
1677
1678 }//end tokenize()
1679
1680
1681 /**
1682 * Performs additional processing after main tokenizing.
1683 *
1684 * This additional processing checks for CASE statements that are using curly
1685 * braces for scope openers and closers. It also turns some T_FUNCTION tokens
1686 * into T_CLOSURE when they are not standard function definitions. It also
1687 * detects short array syntax and converts those square brackets into new tokens.
1688 * It also corrects some usage of the static and class keywords. It also
1689 * assigns tokens to function return types.
1690 *
1691 * @return void
1692 */
1693 protected function processAdditional()
1694 {
1695 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1696 echo "\t*** START ADDITIONAL PHP PROCESSING ***".PHP_EOL;
1697 }
1698
1699 $numTokens = count($this->tokens);
1700 for ($i = ($numTokens - 1); $i >= 0; $i--) {
1701 // Check for any unset scope conditions due to alternate IF/ENDIF syntax.
1702 if (isset($this->tokens[$i]['scope_opener']) === true
1703 && isset($this->tokens[$i]['scope_condition']) === false
1704 ) {
1705 $this->tokens[$i]['scope_condition'] = $this->tokens[$this->tokens[$i]['scope_opener']]['scope_condition'];
1706 }
1707
1708 if ($this->tokens[$i]['code'] === T_FUNCTION) {
1709 /*
1710 Detect functions that are actually closures and
1711 assign them a different token.
1712 */
1713
1714 if (isset($this->tokens[$i]['scope_opener']) === true) {
1715 for ($x = ($i + 1); $x < $numTokens; $x++) {
1716 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1717 && $this->tokens[$x]['code'] !== T_BITWISE_AND
1718 ) {
1719 break;
1720 }
1721 }
1722
1723 if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1724 $this->tokens[$i]['code'] = T_CLOSURE;
1725 $this->tokens[$i]['type'] = 'T_CLOSURE';
1726 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1727 $line = $this->tokens[$i]['line'];
1728 echo "\t* token $i on line $line changed from T_FUNCTION to T_CLOSURE".PHP_EOL;
1729 }
1730
1731 for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1732 if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1733 continue;
1734 }
1735
1736 $this->tokens[$x]['conditions'][$i] = T_CLOSURE;
1737 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1738 $type = $this->tokens[$x]['type'];
1739 echo "\t\t* cleaned $x ($type) *".PHP_EOL;
1740 }
1741 }
1742 }
1743 }//end if
1744
1745 continue;
1746 } else if ($this->tokens[$i]['code'] === T_CLASS && isset($this->tokens[$i]['scope_opener']) === true) {
1747 /*
1748 Detect anonymous classes and assign them a different token.
1749 */
1750
1751 for ($x = ($i + 1); $x < $numTokens; $x++) {
1752 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1753 break;
1754 }
1755 }
1756
1757 if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1758 || $this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET
1759 || $this->tokens[$x]['code'] === T_EXTENDS
1760 || $this->tokens[$x]['code'] === T_IMPLEMENTS
1761 ) {
1762 $this->tokens[$i]['code'] = T_ANON_CLASS;
1763 $this->tokens[$i]['type'] = 'T_ANON_CLASS';
1764 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1765 $line = $this->tokens[$i]['line'];
1766 echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL;
1767 }
1768
1769 if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1770 && isset($this->tokens[$x]['parenthesis_closer']) === true
1771 ) {
1772 $closer = $this->tokens[$x]['parenthesis_closer'];
1773
1774 $this->tokens[$i]['parenthesis_opener'] = $x;
1775 $this->tokens[$i]['parenthesis_closer'] = $closer;
1776 $this->tokens[$i]['parenthesis_owner'] = $i;
1777 $this->tokens[$x]['parenthesis_owner'] = $i;
1778 $this->tokens[$closer]['parenthesis_owner'] = $i;
1779
1780 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1781 $line = $this->tokens[$i]['line'];
1782 echo "\t\t* added parenthesis keys to T_ANON_CLASS token $i on line $line".PHP_EOL;
1783 }
1784 }
1785
1786 for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
1787 if (isset($this->tokens[$x]['conditions'][$i]) === false) {
1788 continue;
1789 }
1790
1791 $this->tokens[$x]['conditions'][$i] = T_ANON_CLASS;
1792 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1793 $type = $this->tokens[$x]['type'];
1794 echo "\t\t* cleaned $x ($type) *".PHP_EOL;
1795 }
1796 }
1797 }//end if
1798
1799 continue;
1800 } else if ($this->tokens[$i]['code'] === T_FN && isset($this->tokens[($i + 1)]) === true) {
1801 // Possible arrow function.
1802 for ($x = ($i + 1); $x < $numTokens; $x++) {
1803 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1804 && $this->tokens[$x]['code'] !== T_BITWISE_AND
1805 ) {
1806 // Non-whitespace content.
1807 break;
1808 }
1809 }
1810
1811 if (isset($this->tokens[$x]) === true && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
1812 $ignore = Util\Tokens::$emptyTokens;
1813 $ignore += [
1814 T_STRING => T_STRING,
1815 T_ARRAY => T_ARRAY,
1816 T_COLON => T_COLON,
1817 T_NS_SEPARATOR => T_NS_SEPARATOR,
1818 T_NULLABLE => T_NULLABLE,
1819 T_CALLABLE => T_CALLABLE,
1820 T_PARENT => T_PARENT,
1821 T_SELF => T_SELF,
1822 ];
1823
1824 $closer = $this->tokens[$x]['parenthesis_closer'];
1825 for ($arrow = ($closer + 1); $arrow < $numTokens; $arrow++) {
1826 if (isset($ignore[$this->tokens[$arrow]['code']]) === false) {
1827 break;
1828 }
1829 }
1830
1831 if ($this->tokens[$arrow]['code'] === T_DOUBLE_ARROW) {
1832 $endTokens = [
1833 T_COLON => true,
1834 T_COMMA => true,
1835 T_SEMICOLON => true,
1836 T_CLOSE_PARENTHESIS => true,
1837 T_CLOSE_SQUARE_BRACKET => true,
1838 T_CLOSE_CURLY_BRACKET => true,
1839 T_CLOSE_SHORT_ARRAY => true,
1840 T_OPEN_TAG => true,
1841 T_CLOSE_TAG => true,
1842 ];
1843
1844 $inTernary = false;
1845
1846 for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
1847 if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
1848 break;
1849 }
1850
1851 if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1852 && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1853 ) {
1854 // We minus 1 here in case the closer can be shared with us.
1855 $scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
1856 continue;
1857 }
1858
1859 if (isset($this->tokens[$scopeCloser]['parenthesis_closer']) === true) {
1860 $scopeCloser = $this->tokens[$scopeCloser]['parenthesis_closer'];
1861 continue;
1862 }
1863
1864 if (isset($this->tokens[$scopeCloser]['bracket_closer']) === true) {
1865 $scopeCloser = $this->tokens[$scopeCloser]['bracket_closer'];
1866 continue;
1867 }
1868
1869 if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1870 $inTernary = true;
1871 continue;
1872 }
1873
1874 if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1875 if ($inTernary === false) {
1876 break;
1877 }
1878
1879 $inTernary = false;
1880 continue;
1881 }
1882 }//end for
1883
1884 if ($scopeCloser !== $numTokens) {
1885 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1886 $line = $this->tokens[$i]['line'];
1887 echo "\t=> token $i on line $line processed as arrow function".PHP_EOL;
1888 }
1889
1890 $this->tokens[$i]['code'] = T_FN;
1891 $this->tokens[$i]['type'] = 'T_FN';
1892 $this->tokens[$i]['scope_condition'] = $i;
1893 $this->tokens[$i]['scope_opener'] = $arrow;
1894 $this->tokens[$i]['scope_closer'] = $scopeCloser;
1895 $this->tokens[$i]['parenthesis_owner'] = $i;
1896 $this->tokens[$i]['parenthesis_opener'] = $x;
1897 $this->tokens[$i]['parenthesis_closer'] = $this->tokens[$x]['parenthesis_closer'];
1898
1899 $this->tokens[$arrow]['code'] = T_FN_ARROW;
1900 $this->tokens[$arrow]['type'] = 'T_FN_ARROW';
1901
1902 $this->tokens[$arrow]['scope_condition'] = $i;
1903 $this->tokens[$arrow]['scope_opener'] = $arrow;
1904 $this->tokens[$arrow]['scope_closer'] = $scopeCloser;
1905 $this->tokens[$scopeCloser]['scope_condition'] = $i;
1906 $this->tokens[$scopeCloser]['scope_opener'] = $arrow;
1907 $this->tokens[$scopeCloser]['scope_closer'] = $scopeCloser;
1908
1909 $opener = $this->tokens[$i]['parenthesis_opener'];
1910 $closer = $this->tokens[$i]['parenthesis_closer'];
1911 $this->tokens[$opener]['parenthesis_owner'] = $i;
1912 $this->tokens[$closer]['parenthesis_owner'] = $i;
1913
1914 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1915 $line = $this->tokens[$arrow]['line'];
1916 echo "\t\t* token $arrow on line $line changed from T_DOUBLE_ARROW to T_FN_ARROW".PHP_EOL;
1917 }
1918 }//end if
1919 }//end if
1920 }//end if
1921 } else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
1922 if (isset($this->tokens[$i]['bracket_closer']) === false) {
1923 continue;
1924 }
1925
1926 // Unless there is a variable or a bracket before this token,
1927 // it is the start of an array being defined using the short syntax.
1928 $isShortArray = false;
1929 $allowed = [
1930 T_CLOSE_SQUARE_BRACKET => T_CLOSE_SQUARE_BRACKET,
1931 T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET,
1932 T_CLOSE_PARENTHESIS => T_CLOSE_PARENTHESIS,
1933 T_VARIABLE => T_VARIABLE,
1934 T_OBJECT_OPERATOR => T_OBJECT_OPERATOR,
1935 T_STRING => T_STRING,
1936 T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING,
1937 ];
1938
1939 for ($x = ($i - 1); $x >= 0; $x--) {
1940 // If we hit a scope opener, the statement has ended
1941 // without finding anything, so it's probably an array
1942 // using PHP 7.1 short list syntax.
1943 if (isset($this->tokens[$x]['scope_opener']) === true) {
1944 $isShortArray = true;
1945 break;
1946 }
1947
1948 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1949 if (isset($allowed[$this->tokens[$x]['code']]) === false) {
1950 $isShortArray = true;
1951 }
1952
1953 break;
1954 }
1955 }
1956
1957 if ($isShortArray === true) {
1958 $this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
1959 $this->tokens[$i]['type'] = 'T_OPEN_SHORT_ARRAY';
1960
1961 $closer = $this->tokens[$i]['bracket_closer'];
1962 $this->tokens[$closer]['code'] = T_CLOSE_SHORT_ARRAY;
1963 $this->tokens[$closer]['type'] = 'T_CLOSE_SHORT_ARRAY';
1964 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1965 $line = $this->tokens[$i]['line'];
1966 echo "\t* token $i on line $line changed from T_OPEN_SQUARE_BRACKET to T_OPEN_SHORT_ARRAY".PHP_EOL;
1967 $line = $this->tokens[$closer]['line'];
1968 echo "\t* token $closer on line $line changed from T_CLOSE_SQUARE_BRACKET to T_CLOSE_SHORT_ARRAY".PHP_EOL;
1969 }
1970 }
1971
1972 continue;
1973 } else if ($this->tokens[$i]['code'] === T_STATIC) {
1974 for ($x = ($i - 1); $x > 0; $x--) {
1975 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1976 break;
1977 }
1978 }
1979
1980 if ($this->tokens[$x]['code'] === T_INSTANCEOF) {
1981 $this->tokens[$i]['code'] = T_STRING;
1982 $this->tokens[$i]['type'] = 'T_STRING';
1983
1984 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1985 $line = $this->tokens[$i]['line'];
1986 echo "\t* token $i on line $line changed from T_STATIC to T_STRING".PHP_EOL;
1987 }
1988 }
1989
1990 continue;
1991 } else if ($this->tokens[$i]['code'] === T_TRUE
1992 || $this->tokens[$i]['code'] === T_FALSE
1993 || $this->tokens[$i]['code'] === T_NULL
1994 ) {
1995 for ($x = ($i + 1); $i < $numTokens; $x++) {
1996 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1997 // Non-whitespace content.
1998 break;
1999 }
2000 }
2001
2002 $context = [
2003 T_OBJECT_OPERATOR => true,
2004 T_NS_SEPARATOR => true,
2005 T_PAAMAYIM_NEKUDOTAYIM => true,
2006 ];
2007 if (isset($context[$this->tokens[$x]['code']]) === true) {
2008 if (PHP_CODESNIFFER_VERBOSITY > 1) {
2009 $line = $this->tokens[$i]['line'];
2010 $type = $this->tokens[$i]['type'];
2011 echo "\t* token $i on line $line changed from $type to T_STRING".PHP_EOL;
2012 }
2013
2014 $this->tokens[$i]['code'] = T_STRING;
2015 $this->tokens[$i]['type'] = 'T_STRING';
2016 }
2017 } else if ($this->tokens[$i]['code'] === T_CONST) {
2018 // Context sensitive keywords support.
2019 for ($x = ($i + 1); $i < $numTokens; $x++) {
2020 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
2021 // Non-whitespace content.
2022 break;
2023 }
2024 }
2025
2026 if ($this->tokens[$x]['code'] !== T_STRING) {
2027 if (PHP_CODESNIFFER_VERBOSITY > 1) {
2028 $line = $this->tokens[$x]['line'];
2029 $type = $this->tokens[$x]['type'];
2030 echo "\t* token $x on line $line changed from $type to T_STRING".PHP_EOL;
2031 }
2032
2033 $this->tokens[$x]['code'] = T_STRING;
2034 $this->tokens[$x]['type'] = 'T_STRING';
2035 }
2036 }//end if
2037
2038 if (($this->tokens[$i]['code'] !== T_CASE
2039 && $this->tokens[$i]['code'] !== T_DEFAULT)
2040 || isset($this->tokens[$i]['scope_opener']) === false
2041 ) {
2042 // Only interested in CASE and DEFAULT statements from here on in.
2043 continue;
2044 }
2045
2046 $scopeOpener = $this->tokens[$i]['scope_opener'];
2047 $scopeCloser = $this->tokens[$i]['scope_closer'];
2048
2049 // If the first char after the opener is a curly brace
2050 // and that brace has been ignored, it is actually
2051 // opening this case statement and the opener and closer are
2052 // probably set incorrectly.
2053 for ($x = ($scopeOpener + 1); $x < $numTokens; $x++) {
2054 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
2055 // Non-whitespace content.
2056 break;
2057 }
2058 }
2059
2060 if ($this->tokens[$x]['code'] === T_CASE || $this->tokens[$x]['code'] === T_DEFAULT) {
2061 // Special case for multiple CASE statements that share the same
2062 // closer. Because we are going backwards through the file, this next
2063 // CASE statement is already fixed, so just use its closer and don't
2064 // worry about fixing anything.
2065 $newCloser = $this->tokens[$x]['scope_closer'];
2066 $this->tokens[$i]['scope_closer'] = $newCloser;
2067 if (PHP_CODESNIFFER_VERBOSITY > 1) {
2068 $oldType = $this->tokens[$scopeCloser]['type'];
2069 $newType = $this->tokens[$newCloser]['type'];
2070 $line = $this->tokens[$i]['line'];
2071 echo "\t* token $i (T_CASE) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
2072 }
2073
2074 continue;
2075 }
2076
2077 if ($this->tokens[$x]['code'] !== T_OPEN_CURLY_BRACKET
2078 || isset($this->tokens[$x]['scope_condition']) === true
2079 ) {
2080 // Not a CASE/DEFAULT with a curly brace opener.
2081 continue;
2082 }
2083
2084 // The closer for this CASE/DEFAULT should be the closing curly brace and
2085 // not whatever it already is. The opener needs to be the opening curly
2086 // brace so everything matches up.
2087 $newCloser = $this->tokens[$x]['bracket_closer'];
2088 foreach ([$i, $x, $newCloser] as $index) {
2089 $this->tokens[$index]['scope_condition'] = $i;
2090 $this->tokens[$index]['scope_opener'] = $x;
2091 $this->tokens[$index]['scope_closer'] = $newCloser;
2092 }
2093
2094 if (PHP_CODESNIFFER_VERBOSITY > 1) {
2095 $line = $this->tokens[$i]['line'];
2096 $tokenType = $this->tokens[$i]['type'];
2097
2098 $oldType = $this->tokens[$scopeOpener]['type'];
2099 $newType = $this->tokens[$x]['type'];
2100 echo "\t* token $i ($tokenType) on line $line opener changed from $scopeOpener ($oldType) to $x ($newType)".PHP_EOL;
2101
2102 $oldType = $this->tokens[$scopeCloser]['type'];
2103 $newType = $this->tokens[$newCloser]['type'];
2104 echo "\t* token $i ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
2105 }
2106
2107 if ($this->tokens[$scopeOpener]['scope_condition'] === $i) {
2108 unset($this->tokens[$scopeOpener]['scope_condition']);
2109 unset($this->tokens[$scopeOpener]['scope_opener']);
2110 unset($this->tokens[$scopeOpener]['scope_closer']);
2111 }
2112
2113 if ($this->tokens[$scopeCloser]['scope_condition'] === $i) {
2114 unset($this->tokens[$scopeCloser]['scope_condition']);
2115 unset($this->tokens[$scopeCloser]['scope_opener']);
2116 unset($this->tokens[$scopeCloser]['scope_closer']);
2117 } else {
2118 // We were using a shared closer. All tokens that were
2119 // sharing this closer with us, except for the scope condition
2120 // and it's opener, need to now point to the new closer.
2121 $condition = $this->tokens[$scopeCloser]['scope_condition'];
2122 $start = ($this->tokens[$condition]['scope_opener'] + 1);
2123 for ($y = $start; $y < $scopeCloser; $y++) {
2124 if (isset($this->tokens[$y]['scope_closer']) === true
2125 && $this->tokens[$y]['scope_closer'] === $scopeCloser
2126 ) {
2127 $this->tokens[$y]['scope_closer'] = $newCloser;
2128
2129 if (PHP_CODESNIFFER_VERBOSITY > 1) {
2130 $line = $this->tokens[$y]['line'];
2131 $tokenType = $this->tokens[$y]['type'];
2132 $oldType = $this->tokens[$scopeCloser]['type'];
2133 $newType = $this->tokens[$newCloser]['type'];
2134 echo "\t\t* token $y ($tokenType) on line $line closer changed from $scopeCloser ($oldType) to $newCloser ($newType)".PHP_EOL;
2135 }
2136 }
2137 }
2138 }//end if
2139
2140 unset($this->tokens[$x]['bracket_opener']);
2141 unset($this->tokens[$x]['bracket_closer']);
2142 unset($this->tokens[$newCloser]['bracket_opener']);
2143 unset($this->tokens[$newCloser]['bracket_closer']);
2144 $this->tokens[$scopeCloser]['conditions'][] = $i;
2145
2146 // Now fix up all the tokens that think they are
2147 // inside the CASE/DEFAULT statement when they are really outside.
2148 for ($x = $newCloser; $x < $scopeCloser; $x++) {
2149 foreach ($this->tokens[$x]['conditions'] as $num => $oldCond) {
2150 if ($oldCond === $this->tokens[$i]['code']) {
2151 $oldConditions = $this->tokens[$x]['conditions'];
2152 unset($this->tokens[$x]['conditions'][$num]);
2153
2154 if (PHP_CODESNIFFER_VERBOSITY > 1) {
2155 $type = $this->tokens[$x]['type'];
2156 $oldConds = '';
2157 foreach ($oldConditions as $condition) {
2158 $oldConds .= Util\Tokens::tokenName($condition).',';
2159 }
2160
2161 $oldConds = rtrim($oldConds, ',');
2162
2163 $newConds = '';
2164 foreach ($this->tokens[$x]['conditions'] as $condition) {
2165 $newConds .= Util\Tokens::tokenName($condition).',';
2166 }
2167
2168 $newConds = rtrim($newConds, ',');
2169
2170 echo "\t\t* cleaned $x ($type) *".PHP_EOL;
2171 echo "\t\t\t=> conditions changed from $oldConds to $newConds".PHP_EOL;
2172 }
2173
2174 break;
2175 }//end if
2176 }//end foreach
2177 }//end for
2178 }//end for
2179
2180 if (PHP_CODESNIFFER_VERBOSITY > 1) {
2181 echo "\t*** END ADDITIONAL PHP PROCESSING ***".PHP_EOL;
2182 }
2183
2184 }//end processAdditional()
2185
2186
2187 /**
2188 * Takes a token produced from <code>token_get_all()</code> and produces a
2189 * more uniform token.
2190 *
2191 * @param string|array $token The token to convert.
2192 *
2193 * @return array The new token.
2194 */
2195 public static function standardiseToken($token)
2196 {
2197 if (isset($token[1]) === false) {
2198 if (isset(self::$resolveTokenCache[$token[0]]) === true) {
2199 return self::$resolveTokenCache[$token[0]];
2200 }
2201 } else {
2202 $cacheKey = null;
2203 if ($token[0] === T_STRING) {
2204 $cacheKey = strtolower($token[1]);
2205 } else if ($token[0] !== T_CURLY_OPEN) {
2206 $cacheKey = $token[0];
2207 }
2208
2209 if ($cacheKey !== null && isset(self::$resolveTokenCache[$cacheKey]) === true) {
2210 $newToken = self::$resolveTokenCache[$cacheKey];
2211 $newToken['content'] = $token[1];
2212 return $newToken;
2213 }
2214 }
2215
2216 if (isset($token[1]) === false) {
2217 return self::resolveSimpleToken($token[0]);
2218 }
2219
2220 if ($token[0] === T_STRING) {
2221 switch ($cacheKey) {
2222 case 'false':
2223 $newToken['type'] = 'T_FALSE';
2224 break;
2225 case 'true':
2226 $newToken['type'] = 'T_TRUE';
2227 break;
2228 case 'null':
2229 $newToken['type'] = 'T_NULL';
2230 break;
2231 case 'self':
2232 $newToken['type'] = 'T_SELF';
2233 break;
2234 case 'parent':
2235 $newToken['type'] = 'T_PARENT';
2236 break;
2237 default:
2238 $newToken['type'] = 'T_STRING';
2239 break;
2240 }
2241
2242 $newToken['code'] = constant($newToken['type']);
2243
2244 self::$resolveTokenCache[$cacheKey] = $newToken;
2245 } else if ($token[0] === T_CURLY_OPEN) {
2246 $newToken = [
2247 'code' => T_OPEN_CURLY_BRACKET,
2248 'type' => 'T_OPEN_CURLY_BRACKET',
2249 ];
2250 } else {
2251 $newToken = [
2252 'code' => $token[0],
2253 'type' => Util\Tokens::tokenName($token[0]),
2254 ];
2255
2256 self::$resolveTokenCache[$token[0]] = $newToken;
2257 }//end if
2258
2259 $newToken['content'] = $token[1];
2260 return $newToken;
2261
2262 }//end standardiseToken()
2263
2264
2265 /**
2266 * Converts simple tokens into a format that conforms to complex tokens
2267 * produced by token_get_all().
2268 *
2269 * Simple tokens are tokens that are not in array form when produced from
2270 * token_get_all().
2271 *
2272 * @param string $token The simple token to convert.
2273 *
2274 * @return array The new token in array format.
2275 */
2276 public static function resolveSimpleToken($token)
2277 {
2278 $newToken = [];
2279
2280 switch ($token) {
2281 case '{':
2282 $newToken['type'] = 'T_OPEN_CURLY_BRACKET';
2283 break;
2284 case '}':
2285 $newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
2286 break;
2287 case '[':
2288 $newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
2289 break;
2290 case ']':
2291 $newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
2292 break;
2293 case '(':
2294 $newToken['type'] = 'T_OPEN_PARENTHESIS';
2295 break;
2296 case ')':
2297 $newToken['type'] = 'T_CLOSE_PARENTHESIS';
2298 break;
2299 case ':':
2300 $newToken['type'] = 'T_COLON';
2301 break;
2302 case '.':
2303 $newToken['type'] = 'T_STRING_CONCAT';
2304 break;
2305 case ';':
2306 $newToken['type'] = 'T_SEMICOLON';
2307 break;
2308 case '=':
2309 $newToken['type'] = 'T_EQUAL';
2310 break;
2311 case '*':
2312 $newToken['type'] = 'T_MULTIPLY';
2313 break;
2314 case '/':
2315 $newToken['type'] = 'T_DIVIDE';
2316 break;
2317 case '+':
2318 $newToken['type'] = 'T_PLUS';
2319 break;
2320 case '-':
2321 $newToken['type'] = 'T_MINUS';
2322 break;
2323 case '%':
2324 $newToken['type'] = 'T_MODULUS';
2325 break;
2326 case '^':
2327 $newToken['type'] = 'T_BITWISE_XOR';
2328 break;
2329 case '&':
2330 $newToken['type'] = 'T_BITWISE_AND';
2331 break;
2332 case '|':
2333 $newToken['type'] = 'T_BITWISE_OR';
2334 break;
2335 case '~':
2336 $newToken['type'] = 'T_BITWISE_NOT';
2337 break;
2338 case '<':
2339 $newToken['type'] = 'T_LESS_THAN';
2340 break;
2341 case '>':
2342 $newToken['type'] = 'T_GREATER_THAN';
2343 break;
2344 case '!':
2345 $newToken['type'] = 'T_BOOLEAN_NOT';
2346 break;
2347 case ',':
2348 $newToken['type'] = 'T_COMMA';
2349 break;
2350 case '@':
2351 $newToken['type'] = 'T_ASPERAND';
2352 break;
2353 case '$':
2354 $newToken['type'] = 'T_DOLLAR';
2355 break;
2356 case '`':
2357 $newToken['type'] = 'T_BACKTICK';
2358 break;
2359 default:
2360 $newToken['type'] = 'T_NONE';
2361 break;
2362 }//end switch
2363
2364 $newToken['code'] = constant($newToken['type']);
2365 $newToken['content'] = $token;
2366
2367 self::$resolveTokenCache[$token] = $newToken;
2368 return $newToken;
2369
2370 }//end resolveSimpleToken()
2371
2372
2373 }//end class
2374