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 / Tokenizer.php

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

1,683 lines 74.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The base tokenizer class.
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\Exceptions\TokenizerException;
13 use PHP_CodeSniffer\Util;
14
15 abstract class Tokenizer
16 {
17
18 /**
19 * The config data for the run.
20 *
21 * @var \PHP_CodeSniffer\Config
22 */
23 protected $config = null;
24
25 /**
26 * The EOL char used in the content.
27 *
28 * @var string
29 */
30 protected $eolChar = [];
31
32 /**
33 * A token-based representation of the content.
34 *
35 * @var array
36 */
37 protected $tokens = [];
38
39 /**
40 * The number of tokens in the tokens array.
41 *
42 * @var integer
43 */
44 protected $numTokens = 0;
45
46 /**
47 * A list of tokens that are allowed to open a scope.
48 *
49 * @var array
50 */
51 public $scopeOpeners = [];
52
53 /**
54 * A list of tokens that end the scope.
55 *
56 * @var array
57 */
58 public $endScopeTokens = [];
59
60 /**
61 * Known lengths of tokens.
62 *
63 * @var array<int, int>
64 */
65 public $knownLengths = [];
66
67 /**
68 * A list of lines being ignored due to error suppression comments.
69 *
70 * @var array
71 */
72 public $ignoredLines = [];
73
74
75 /**
76 * Initialise and run the tokenizer.
77 *
78 * @param string $content The content to tokenize,
79 * @param \PHP_CodeSniffer\Config | null $config The config data for the run.
80 * @param string $eolChar The EOL char used in the content.
81 *
82 * @return void
83 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the file appears to be minified.
84 */
85 public function __construct($content, $config, $eolChar='\n')
86 {
87 $this->eolChar = $eolChar;
88
89 $this->config = $config;
90 $this->tokens = $this->tokenize($content);
91
92 if ($config === null) {
93 return;
94 }
95
96 $this->createPositionMap();
97 $this->createTokenMap();
98 $this->createParenthesisNestingMap();
99 $this->createScopeMap();
100 $this->createLevelMap();
101
102 // Allow the tokenizer to do additional processing if required.
103 $this->processAdditional();
104
105 }//end __construct()
106
107
108 /**
109 * Checks the content to see if it looks minified.
110 *
111 * @param string $content The content to tokenize.
112 * @param string $eolChar The EOL char used in the content.
113 *
114 * @return boolean
115 */
116 protected function isMinifiedContent($content, $eolChar='\n')
117 {
118 // Minified files often have a very large number of characters per line
119 // and cause issues when tokenizing.
120 $numChars = strlen($content);
121 $numLines = (substr_count($content, $eolChar) + 1);
122 $average = ($numChars / $numLines);
123 if ($average > 100) {
124 return true;
125 }
126
127 return false;
128
129 }//end isMinifiedContent()
130
131
132 /**
133 * Gets the array of tokens.
134 *
135 * @return array
136 */
137 public function getTokens()
138 {
139 return $this->tokens;
140
141 }//end getTokens()
142
143
144 /**
145 * Creates an array of tokens when given some content.
146 *
147 * @param string $string The string to tokenize.
148 *
149 * @return array
150 */
151 abstract protected function tokenize($string);
152
153
154 /**
155 * Performs additional processing after main tokenizing.
156 *
157 * @return void
158 */
159 abstract protected function processAdditional();
160
161
162 /**
163 * Sets token position information.
164 *
165 * Can also convert tabs into spaces. Each tab can represent between
166 * 1 and $width spaces, so this cannot be a straight string replace.
167 *
168 * @return void
169 */
170 private function createPositionMap()
171 {
172 $currColumn = 1;
173 $lineNumber = 1;
174 $eolLen = strlen($this->eolChar);
175 $ignoring = null;
176 $inTests = defined('PHP_CODESNIFFER_IN_TESTS');
177
178 $checkEncoding = false;
179 if (function_exists('iconv_strlen') === true) {
180 $checkEncoding = true;
181 }
182
183 $checkAnnotations = $this->config->annotations;
184 $encoding = $this->config->encoding;
185 $tabWidth = $this->config->tabWidth;
186
187 $tokensWithTabs = [
188 T_WHITESPACE => true,
189 T_COMMENT => true,
190 T_DOC_COMMENT => true,
191 T_DOC_COMMENT_WHITESPACE => true,
192 T_DOC_COMMENT_STRING => true,
193 T_CONSTANT_ENCAPSED_STRING => true,
194 T_DOUBLE_QUOTED_STRING => true,
195 T_HEREDOC => true,
196 T_NOWDOC => true,
197 T_INLINE_HTML => true,
198 ];
199
200 $this->numTokens = count($this->tokens);
201 for ($i = 0; $i < $this->numTokens; $i++) {
202 $this->tokens[$i]['line'] = $lineNumber;
203 $this->tokens[$i]['column'] = $currColumn;
204
205 if (isset($this->knownLengths[$this->tokens[$i]['code']]) === true) {
206 // There are no tabs in the tokens we know the length of.
207 $length = $this->knownLengths[$this->tokens[$i]['code']];
208 $currColumn += $length;
209 } else if ($tabWidth === 0
210 || isset($tokensWithTabs[$this->tokens[$i]['code']]) === false
211 || strpos($this->tokens[$i]['content'], "\t") === false
212 ) {
213 // There are no tabs in this content, or we aren't replacing them.
214 if ($checkEncoding === true) {
215 // Not using the default encoding, so take a bit more care.
216 $oldLevel = error_reporting();
217 error_reporting(0);
218 $length = iconv_strlen($this->tokens[$i]['content'], $encoding);
219 error_reporting($oldLevel);
220
221 if ($length === false) {
222 // String contained invalid characters, so revert to default.
223 $length = strlen($this->tokens[$i]['content']);
224 }
225 } else {
226 $length = strlen($this->tokens[$i]['content']);
227 }
228
229 $currColumn += $length;
230 } else {
231 $this->replaceTabsInToken($this->tokens[$i]);
232 $length = $this->tokens[$i]['length'];
233 $currColumn += $length;
234 }//end if
235
236 $this->tokens[$i]['length'] = $length;
237
238 if (isset($this->knownLengths[$this->tokens[$i]['code']]) === false
239 && strpos($this->tokens[$i]['content'], $this->eolChar) !== false
240 ) {
241 $lineNumber++;
242 $currColumn = 1;
243
244 // Newline chars are not counted in the token length.
245 $this->tokens[$i]['length'] -= $eolLen;
246 }
247
248 if ($this->tokens[$i]['code'] === T_COMMENT
249 || $this->tokens[$i]['code'] === T_DOC_COMMENT_STRING
250 || $this->tokens[$i]['code'] === T_DOC_COMMENT_TAG
251 || ($inTests === true && $this->tokens[$i]['code'] === T_INLINE_HTML)
252 ) {
253 $commentText = ltrim($this->tokens[$i]['content'], " \t/*");
254 $commentText = rtrim($commentText, " */\t\r\n");
255 $commentTextLower = strtolower($commentText);
256 if (strpos($commentText, '@codingStandards') !== false) {
257 // If this comment is the only thing on the line, it tells us
258 // to ignore the following line. If the line contains other content
259 // then we are just ignoring this one single line.
260 $ownLine = false;
261 if ($i > 0) {
262 for ($prev = ($i - 1); $prev >= 0; $prev--) {
263 if ($this->tokens[$prev]['code'] === T_WHITESPACE) {
264 continue;
265 }
266
267 break;
268 }
269
270 if ($this->tokens[$prev]['line'] !== $this->tokens[$i]['line']) {
271 $ownLine = true;
272 }
273 }
274
275 if ($ignoring === null
276 && strpos($commentText, '@codingStandardsIgnoreStart') !== false
277 ) {
278 $ignoring = ['.all' => true];
279 if ($ownLine === true) {
280 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
281 }
282 } else if ($ignoring !== null
283 && strpos($commentText, '@codingStandardsIgnoreEnd') !== false
284 ) {
285 if ($ownLine === true) {
286 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
287 } else {
288 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
289 }
290
291 $ignoring = null;
292 } else if ($ignoring === null
293 && strpos($commentText, '@codingStandardsIgnoreLine') !== false
294 ) {
295 $ignoring = ['.all' => true];
296 if ($ownLine === true) {
297 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
298 $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $ignoring;
299 } else {
300 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
301 }
302
303 $ignoring = null;
304 }//end if
305 } else if (substr($commentTextLower, 0, 6) === 'phpcs:'
306 || substr($commentTextLower, 0, 7) === '@phpcs:'
307 ) {
308 // If the @phpcs: syntax is being used, strip the @ to make
309 // comparisons easier.
310 if ($commentText[0] === '@') {
311 $commentText = substr($commentText, 1);
312 $commentTextLower = strtolower($commentText);
313 }
314
315 // If there is a comment on the end, strip it off.
316 $commentStart = strpos($commentTextLower, ' --');
317 if ($commentStart !== false) {
318 $commentText = substr($commentText, 0, $commentStart);
319 $commentTextLower = strtolower($commentText);
320 }
321
322 // If this comment is the only thing on the line, it tells us
323 // to ignore the following line. If the line contains other content
324 // then we are just ignoring this one single line.
325 $lineHasOtherContent = false;
326 $lineHasOtherTokens = false;
327 if ($i > 0) {
328 for ($prev = ($i - 1); $prev > 0; $prev--) {
329 if ($this->tokens[$prev]['line'] !== $this->tokens[$i]['line']) {
330 // Changed lines.
331 break;
332 }
333
334 if ($this->tokens[$prev]['code'] === T_WHITESPACE
335 || ($this->tokens[$prev]['code'] === T_INLINE_HTML
336 && trim($this->tokens[$prev]['content']) === '')
337 ) {
338 continue;
339 }
340
341 $lineHasOtherTokens = true;
342
343 if ($this->tokens[$prev]['code'] === T_OPEN_TAG) {
344 continue;
345 }
346
347 $lineHasOtherContent = true;
348 break;
349 }//end for
350
351 $changedLines = false;
352 for ($next = $i; $next < $this->numTokens; $next++) {
353 if ($changedLines === true) {
354 // Changed lines.
355 break;
356 }
357
358 if (isset($this->knownLengths[$this->tokens[$next]['code']]) === false
359 && strpos($this->tokens[$next]['content'], $this->eolChar) !== false
360 ) {
361 // Last token on the current line.
362 $changedLines = true;
363 }
364
365 if ($next === $i) {
366 continue;
367 }
368
369 if ($this->tokens[$next]['code'] === T_WHITESPACE
370 || ($this->tokens[$next]['code'] === T_INLINE_HTML
371 && trim($this->tokens[$next]['content']) === '')
372 ) {
373 continue;
374 }
375
376 $lineHasOtherTokens = true;
377
378 if ($this->tokens[$next]['code'] === T_CLOSE_TAG) {
379 continue;
380 }
381
382 $lineHasOtherContent = true;
383 break;
384 }//end for
385 }//end if
386
387 if (substr($commentTextLower, 0, 9) === 'phpcs:set') {
388 // Ignore standards for complete lines that change sniff settings.
389 if ($lineHasOtherTokens === false) {
390 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
391 }
392
393 // Need to maintain case here, to get the correct sniff code.
394 $parts = explode(' ', substr($commentText, 10));
395 if (count($parts) >= 2) {
396 $sniffParts = explode('.', $parts[0]);
397 if (count($sniffParts) >= 3) {
398 $this->tokens[$i]['sniffCode'] = array_shift($parts);
399 $this->tokens[$i]['sniffProperty'] = array_shift($parts);
400 $this->tokens[$i]['sniffPropertyValue'] = rtrim(implode(' ', $parts), " */\r\n");
401 }
402 }
403
404 $this->tokens[$i]['code'] = T_PHPCS_SET;
405 $this->tokens[$i]['type'] = 'T_PHPCS_SET';
406 } else if (substr($commentTextLower, 0, 16) === 'phpcs:ignorefile') {
407 // The whole file will be ignored, but at least set the correct token.
408 $this->tokens[$i]['code'] = T_PHPCS_IGNORE_FILE;
409 $this->tokens[$i]['type'] = 'T_PHPCS_IGNORE_FILE';
410 } else if (substr($commentTextLower, 0, 13) === 'phpcs:disable') {
411 if ($lineHasOtherContent === false) {
412 // Completely ignore the comment line.
413 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
414 }
415
416 if ($ignoring === null) {
417 $ignoring = [];
418 }
419
420 $disabledSniffs = [];
421
422 $additionalText = substr($commentText, 14);
423 if ($additionalText === false) {
424 $ignoring = ['.all' => true];
425 } else {
426 $parts = explode(',', substr($commentText, 13));
427 foreach ($parts as $sniffCode) {
428 $sniffCode = trim($sniffCode);
429 $disabledSniffs[$sniffCode] = true;
430 $ignoring[$sniffCode] = true;
431
432 // This newly disabled sniff might be disabling an existing
433 // enabled exception that we are tracking.
434 if (isset($ignoring['.except']) === true) {
435 foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) {
436 if ($ignoredSniffCode === $sniffCode
437 || strpos($ignoredSniffCode, $sniffCode.'.') === 0
438 ) {
439 unset($ignoring['.except'][$ignoredSniffCode]);
440 }
441 }
442
443 if (empty($ignoring['.except']) === true) {
444 unset($ignoring['.except']);
445 }
446 }
447 }//end foreach
448 }//end if
449
450 $this->tokens[$i]['code'] = T_PHPCS_DISABLE;
451 $this->tokens[$i]['type'] = 'T_PHPCS_DISABLE';
452 $this->tokens[$i]['sniffCodes'] = $disabledSniffs;
453 } else if (substr($commentTextLower, 0, 12) === 'phpcs:enable') {
454 if ($ignoring !== null) {
455 $enabledSniffs = [];
456
457 $additionalText = substr($commentText, 13);
458 if ($additionalText === false) {
459 $ignoring = null;
460 } else {
461 $parts = explode(',', substr($commentText, 13));
462 foreach ($parts as $sniffCode) {
463 $sniffCode = trim($sniffCode);
464 $enabledSniffs[$sniffCode] = true;
465
466 // This new enabled sniff might remove previously disabled
467 // sniffs if it is actually a standard or category of sniffs.
468 foreach (array_keys($ignoring) as $ignoredSniffCode) {
469 if ($ignoredSniffCode === $sniffCode
470 || strpos($ignoredSniffCode, $sniffCode.'.') === 0
471 ) {
472 unset($ignoring[$ignoredSniffCode]);
473 }
474 }
475
476 // This new enabled sniff might be able to clear up
477 // previously enabled sniffs if it is actually a standard or
478 // category of sniffs.
479 if (isset($ignoring['.except']) === true) {
480 foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) {
481 if ($ignoredSniffCode === $sniffCode
482 || strpos($ignoredSniffCode, $sniffCode.'.') === 0
483 ) {
484 unset($ignoring['.except'][$ignoredSniffCode]);
485 }
486 }
487 }
488 }//end foreach
489
490 if (empty($ignoring) === true) {
491 $ignoring = null;
492 } else {
493 if (isset($ignoring['.except']) === true) {
494 $ignoring['.except'] += $enabledSniffs;
495 } else {
496 $ignoring['.except'] = $enabledSniffs;
497 }
498 }
499 }//end if
500
501 if ($lineHasOtherContent === false) {
502 // Completely ignore the comment line.
503 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
504 } else {
505 // The comment is on the same line as the code it is ignoring,
506 // so respect the new ignore rules.
507 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
508 }
509
510 $this->tokens[$i]['sniffCodes'] = $enabledSniffs;
511 }//end if
512
513 $this->tokens[$i]['code'] = T_PHPCS_ENABLE;
514 $this->tokens[$i]['type'] = 'T_PHPCS_ENABLE';
515 } else if (substr($commentTextLower, 0, 12) === 'phpcs:ignore') {
516 $ignoreRules = [];
517
518 $additionalText = substr($commentText, 13);
519 if ($additionalText === false) {
520 $ignoreRules = ['.all' => true];
521 } else {
522 $parts = explode(',', substr($commentText, 13));
523 foreach ($parts as $sniffCode) {
524 $ignoreRules[trim($sniffCode)] = true;
525 }
526 }
527
528 $this->tokens[$i]['code'] = T_PHPCS_IGNORE;
529 $this->tokens[$i]['type'] = 'T_PHPCS_IGNORE';
530 $this->tokens[$i]['sniffCodes'] = $ignoreRules;
531
532 if ($ignoring !== null) {
533 $ignoreRules += $ignoring;
534 }
535
536 if ($lineHasOtherContent === false) {
537 // Completely ignore the comment line, and set the following
538 // line to include the ignore rules we've set.
539 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
540 $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $ignoreRules;
541 } else {
542 // The comment is on the same line as the code it is ignoring,
543 // so respect the ignore rules it set.
544 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreRules;
545 }
546 }//end if
547 }//end if
548 }//end if
549
550 if ($ignoring !== null && isset($this->ignoredLines[$this->tokens[$i]['line']]) === false) {
551 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
552 }
553 }//end for
554
555 // If annotations are being ignored, we clear out all the ignore rules
556 // but leave the annotations tokenized as normal.
557 if ($checkAnnotations === false) {
558 $this->ignoredLines = [];
559 }
560
561 }//end createPositionMap()
562
563
564 /**
565 * Replaces tabs in original token content with spaces.
566 *
567 * Each tab can represent between 1 and $config->tabWidth spaces,
568 * so this cannot be a straight string replace. The original content
569 * is placed into an orig_content index and the new token length is also
570 * set in the length index.
571 *
572 * @param array $token The token to replace tabs inside.
573 * @param string $prefix The character to use to represent the start of a tab.
574 * @param string $padding The character to use to represent the end of a tab.
575 * @param int $tabWidth The number of spaces each tab represents.
576 *
577 * @return void
578 */
579 public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth=null)
580 {
581 $checkEncoding = false;
582 if (function_exists('iconv_strlen') === true) {
583 $checkEncoding = true;
584 }
585
586 $currColumn = $token['column'];
587 if ($tabWidth === null) {
588 $tabWidth = $this->config->tabWidth;
589 if ($tabWidth === 0) {
590 $tabWidth = 1;
591 }
592 }
593
594 if (rtrim($token['content'], "\t") === '') {
595 // String only contains tabs, so we can shortcut the process.
596 $numTabs = strlen($token['content']);
597
598 $firstTabSize = ($tabWidth - (($currColumn - 1) % $tabWidth));
599 $length = ($firstTabSize + ($tabWidth * ($numTabs - 1)));
600 $newContent = $prefix.str_repeat($padding, ($length - 1));
601 } else {
602 // We need to determine the length of each tab.
603 $tabs = explode("\t", $token['content']);
604
605 $numTabs = (count($tabs) - 1);
606 $tabNum = 0;
607 $newContent = '';
608 $length = 0;
609
610 foreach ($tabs as $content) {
611 if ($content !== '') {
612 $newContent .= $content;
613 if ($checkEncoding === true) {
614 // Not using the default encoding, so take a bit more care.
615 $oldLevel = error_reporting();
616 error_reporting(0);
617 $contentLength = iconv_strlen($content, $this->config->encoding);
618 error_reporting($oldLevel);
619 if ($contentLength === false) {
620 // String contained invalid characters, so revert to default.
621 $contentLength = strlen($content);
622 }
623 } else {
624 $contentLength = strlen($content);
625 }
626
627 $currColumn += $contentLength;
628 $length += $contentLength;
629 }
630
631 // The last piece of content does not have a tab after it.
632 if ($tabNum === $numTabs) {
633 break;
634 }
635
636 // Process the tab that comes after the content.
637 $lastCurrColumn = $currColumn;
638 $tabNum++;
639
640 // Move the pointer to the next tab stop.
641 if (($currColumn % $tabWidth) === 0) {
642 // This is the first tab, and we are already at a
643 // tab stop, so this tab counts as a single space.
644 $currColumn++;
645 } else {
646 $currColumn++;
647 while (($currColumn % $tabWidth) !== 0) {
648 $currColumn++;
649 }
650
651 $currColumn++;
652 }
653
654 $length += ($currColumn - $lastCurrColumn);
655 $newContent .= $prefix.str_repeat($padding, ($currColumn - $lastCurrColumn - 1));
656 }//end foreach
657 }//end if
658
659 $token['orig_content'] = $token['content'];
660 $token['content'] = $newContent;
661 $token['length'] = $length;
662
663 }//end replaceTabsInToken()
664
665
666 /**
667 * Creates a map of brackets positions.
668 *
669 * @return void
670 */
671 private function createTokenMap()
672 {
673 if (PHP_CODESNIFFER_VERBOSITY > 1) {
674 echo "\t*** START TOKEN MAP ***".PHP_EOL;
675 }
676
677 $squareOpeners = [];
678 $curlyOpeners = [];
679 $this->numTokens = count($this->tokens);
680
681 $openers = [];
682 $openOwner = null;
683
684 for ($i = 0; $i < $this->numTokens; $i++) {
685 /*
686 Parenthesis mapping.
687 */
688
689 if (isset(Util\Tokens::$parenthesisOpeners[$this->tokens[$i]['code']]) === true) {
690 $this->tokens[$i]['parenthesis_opener'] = null;
691 $this->tokens[$i]['parenthesis_closer'] = null;
692 $this->tokens[$i]['parenthesis_owner'] = $i;
693 $openOwner = $i;
694
695 if (PHP_CODESNIFFER_VERBOSITY > 1) {
696 echo str_repeat("\t", (count($openers) + 1));
697 echo "=> Found parenthesis owner at $i".PHP_EOL;
698 }
699 } else if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
700 $openers[] = $i;
701 $this->tokens[$i]['parenthesis_opener'] = $i;
702 if ($openOwner !== null) {
703 if (PHP_CODESNIFFER_VERBOSITY > 1) {
704 echo str_repeat("\t", count($openers));
705 echo "=> Found parenthesis opener at $i for $openOwner".PHP_EOL;
706 }
707
708 $this->tokens[$openOwner]['parenthesis_opener'] = $i;
709 $this->tokens[$i]['parenthesis_owner'] = $openOwner;
710 $openOwner = null;
711 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
712 echo str_repeat("\t", count($openers));
713 echo "=> Found unowned parenthesis opener at $i".PHP_EOL;
714 }
715 } else if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
716 // Did we set an owner for this set of parenthesis?
717 $numOpeners = count($openers);
718 if ($numOpeners !== 0) {
719 $opener = array_pop($openers);
720 if (isset($this->tokens[$opener]['parenthesis_owner']) === true) {
721 $owner = $this->tokens[$opener]['parenthesis_owner'];
722
723 $this->tokens[$owner]['parenthesis_closer'] = $i;
724 $this->tokens[$i]['parenthesis_owner'] = $owner;
725
726 if (PHP_CODESNIFFER_VERBOSITY > 1) {
727 echo str_repeat("\t", (count($openers) + 1));
728 echo "=> Found parenthesis closer at $i for $owner".PHP_EOL;
729 }
730 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
731 echo str_repeat("\t", (count($openers) + 1));
732 echo "=> Found unowned parenthesis closer at $i for $opener".PHP_EOL;
733 }
734
735 $this->tokens[$i]['parenthesis_opener'] = $opener;
736 $this->tokens[$i]['parenthesis_closer'] = $i;
737 $this->tokens[$opener]['parenthesis_closer'] = $i;
738 }//end if
739 }//end if
740
741 /*
742 Bracket mapping.
743 */
744
745 switch ($this->tokens[$i]['code']) {
746 case T_OPEN_SQUARE_BRACKET:
747 $squareOpeners[] = $i;
748
749 if (PHP_CODESNIFFER_VERBOSITY > 1) {
750 echo str_repeat("\t", count($squareOpeners));
751 echo str_repeat("\t", count($curlyOpeners));
752 echo "=> Found square bracket opener at $i".PHP_EOL;
753 }
754 break;
755 case T_OPEN_CURLY_BRACKET:
756 if (isset($this->tokens[$i]['scope_closer']) === false) {
757 $curlyOpeners[] = $i;
758
759 if (PHP_CODESNIFFER_VERBOSITY > 1) {
760 echo str_repeat("\t", count($squareOpeners));
761 echo str_repeat("\t", count($curlyOpeners));
762 echo "=> Found curly bracket opener at $i".PHP_EOL;
763 }
764 }
765 break;
766 case T_CLOSE_SQUARE_BRACKET:
767 if (empty($squareOpeners) === false) {
768 $opener = array_pop($squareOpeners);
769 $this->tokens[$i]['bracket_opener'] = $opener;
770 $this->tokens[$i]['bracket_closer'] = $i;
771 $this->tokens[$opener]['bracket_opener'] = $opener;
772 $this->tokens[$opener]['bracket_closer'] = $i;
773
774 if (PHP_CODESNIFFER_VERBOSITY > 1) {
775 echo str_repeat("\t", count($squareOpeners));
776 echo str_repeat("\t", count($curlyOpeners));
777 echo "\t=> Found square bracket closer at $i for $opener".PHP_EOL;
778 }
779 }
780 break;
781 case T_CLOSE_CURLY_BRACKET:
782 if (empty($curlyOpeners) === false
783 && isset($this->tokens[$i]['scope_opener']) === false
784 ) {
785 $opener = array_pop($curlyOpeners);
786 $this->tokens[$i]['bracket_opener'] = $opener;
787 $this->tokens[$i]['bracket_closer'] = $i;
788 $this->tokens[$opener]['bracket_opener'] = $opener;
789 $this->tokens[$opener]['bracket_closer'] = $i;
790
791 if (PHP_CODESNIFFER_VERBOSITY > 1) {
792 echo str_repeat("\t", count($squareOpeners));
793 echo str_repeat("\t", count($curlyOpeners));
794 echo "\t=> Found curly bracket closer at $i for $opener".PHP_EOL;
795 }
796 }
797 break;
798 default:
799 continue 2;
800 }//end switch
801 }//end for
802
803 // Cleanup for any openers that we didn't find closers for.
804 // This typically means there was a syntax error breaking things.
805 foreach ($openers as $opener) {
806 unset($this->tokens[$opener]['parenthesis_opener']);
807 unset($this->tokens[$opener]['parenthesis_owner']);
808 }
809
810 if (PHP_CODESNIFFER_VERBOSITY > 1) {
811 echo "\t*** END TOKEN MAP ***".PHP_EOL;
812 }
813
814 }//end createTokenMap()
815
816
817 /**
818 * Creates a map for the parenthesis tokens that surround other tokens.
819 *
820 * @return void
821 */
822 private function createParenthesisNestingMap()
823 {
824 $map = [];
825 for ($i = 0; $i < $this->numTokens; $i++) {
826 if (isset($this->tokens[$i]['parenthesis_opener']) === true
827 && $i === $this->tokens[$i]['parenthesis_opener']
828 ) {
829 if (empty($map) === false) {
830 $this->tokens[$i]['nested_parenthesis'] = $map;
831 }
832
833 if (isset($this->tokens[$i]['parenthesis_closer']) === true) {
834 $map[$this->tokens[$i]['parenthesis_opener']]
835 = $this->tokens[$i]['parenthesis_closer'];
836 }
837 } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
838 && $i === $this->tokens[$i]['parenthesis_closer']
839 ) {
840 array_pop($map);
841 if (empty($map) === false) {
842 $this->tokens[$i]['nested_parenthesis'] = $map;
843 }
844 } else {
845 if (empty($map) === false) {
846 $this->tokens[$i]['nested_parenthesis'] = $map;
847 }
848 }//end if
849 }//end for
850
851 }//end createParenthesisNestingMap()
852
853
854 /**
855 * Creates a scope map of tokens that open scopes.
856 *
857 * @return void
858 * @see recurseScopeMap()
859 */
860 private function createScopeMap()
861 {
862 if (PHP_CODESNIFFER_VERBOSITY > 1) {
863 echo "\t*** START SCOPE MAP ***".PHP_EOL;
864 }
865
866 for ($i = 0; $i < $this->numTokens; $i++) {
867 // Check to see if the current token starts a new scope.
868 if (isset($this->scopeOpeners[$this->tokens[$i]['code']]) === true) {
869 if (PHP_CODESNIFFER_VERBOSITY > 1) {
870 $type = $this->tokens[$i]['type'];
871 $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
872 echo "\tStart scope map at $i:$type => $content".PHP_EOL;
873 }
874
875 if (isset($this->tokens[$i]['scope_condition']) === true) {
876 if (PHP_CODESNIFFER_VERBOSITY > 1) {
877 echo "\t* already processed, skipping *".PHP_EOL;
878 }
879
880 continue;
881 }
882
883 $i = $this->recurseScopeMap($i);
884 }//end if
885 }//end for
886
887 if (PHP_CODESNIFFER_VERBOSITY > 1) {
888 echo "\t*** END SCOPE MAP ***".PHP_EOL;
889 }
890
891 }//end createScopeMap()
892
893
894 /**
895 * Recurses though the scope openers to build a scope map.
896 *
897 * @param int $stackPtr The position in the stack of the token that
898 * opened the scope (eg. an IF token or FOR token).
899 * @param int $depth How many scope levels down we are.
900 * @param int $ignore How many curly braces we are ignoring.
901 *
902 * @return int The position in the stack that closed the scope.
903 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the nesting level gets too deep.
904 */
905 private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0)
906 {
907 if (PHP_CODESNIFFER_VERBOSITY > 1) {
908 echo str_repeat("\t", $depth);
909 echo "=> Begin scope map recursion at token $stackPtr with depth $depth".PHP_EOL;
910 }
911
912 $opener = null;
913 $currType = $this->tokens[$stackPtr]['code'];
914 $startLine = $this->tokens[$stackPtr]['line'];
915
916 // We will need this to restore the value if we end up
917 // returning a token ID that causes our calling function to go back
918 // over already ignored braces.
919 $originalIgnore = $ignore;
920
921 // If the start token for this scope opener is the same as
922 // the scope token, we have already found our opener.
923 if (isset($this->scopeOpeners[$currType]['start'][$currType]) === true) {
924 $opener = $stackPtr;
925 }
926
927 for ($i = ($stackPtr + 1); $i < $this->numTokens; $i++) {
928 $tokenType = $this->tokens[$i]['code'];
929
930 if (PHP_CODESNIFFER_VERBOSITY > 1) {
931 $type = $this->tokens[$i]['type'];
932 $line = $this->tokens[$i]['line'];
933 $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
934
935 echo str_repeat("\t", $depth);
936 echo "Process token $i on line $line [";
937 if ($opener !== null) {
938 echo "opener:$opener;";
939 }
940
941 if ($ignore > 0) {
942 echo "ignore=$ignore;";
943 }
944
945 echo "]: $type => $content".PHP_EOL;
946 }//end if
947
948 // Very special case for IF statements in PHP that can be defined without
949 // scope tokens. E.g., if (1) 1; 1 ? (1 ? 1 : 1) : 1;
950 // If an IF statement below this one has an opener but no
951 // keyword, the opener will be incorrectly assigned to this IF statement.
952 // The same case also applies to USE statements, which don't have to have
953 // openers, so a following USE statement can cause an incorrect brace match.
954 if (($currType === T_IF || $currType === T_ELSE || $currType === T_USE)
955 && $opener === null
956 && ($this->tokens[$i]['code'] === T_SEMICOLON
957 || $this->tokens[$i]['code'] === T_CLOSE_TAG)
958 ) {
959 if (PHP_CODESNIFFER_VERBOSITY > 1) {
960 $type = $this->tokens[$stackPtr]['type'];
961 echo str_repeat("\t", $depth);
962 if ($this->tokens[$i]['code'] === T_SEMICOLON) {
963 $closerType = 'semicolon';
964 } else {
965 $closerType = 'close tag';
966 }
967
968 echo "=> Found $closerType before scope opener for $stackPtr:$type, bailing".PHP_EOL;
969 }
970
971 return $i;
972 }
973
974 // Special case for PHP control structures that have no braces.
975 // If we find a curly brace closer before we find the opener,
976 // we're not going to find an opener. That closer probably belongs to
977 // a control structure higher up.
978 if ($opener === null
979 && $ignore === 0
980 && $tokenType === T_CLOSE_CURLY_BRACKET
981 && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
982 ) {
983 if (PHP_CODESNIFFER_VERBOSITY > 1) {
984 $type = $this->tokens[$stackPtr]['type'];
985 echo str_repeat("\t", $depth);
986 echo "=> Found curly brace closer before scope opener for $stackPtr:$type, bailing".PHP_EOL;
987 }
988
989 return ($i - 1);
990 }
991
992 if ($opener !== null
993 && (isset($this->tokens[$i]['scope_opener']) === false
994 || $this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true)
995 && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
996 ) {
997 if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) {
998 // The last opening bracket must have been for a string
999 // offset or alike, so let's ignore it.
1000 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1001 echo str_repeat("\t", $depth);
1002 echo '* finished ignoring curly brace *'.PHP_EOL;
1003 }
1004
1005 $ignore--;
1006 continue;
1007 } else if ($this->tokens[$opener]['code'] === T_OPEN_CURLY_BRACKET
1008 && $tokenType !== T_CLOSE_CURLY_BRACKET
1009 ) {
1010 // The opener is a curly bracket so the closer must be a curly bracket as well.
1011 // We ignore this closer to handle cases such as T_ELSE or T_ELSEIF being considered
1012 // a closer of T_IF when it should not.
1013 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1014 $type = $this->tokens[$stackPtr]['type'];
1015 echo str_repeat("\t", $depth);
1016 echo "=> Ignoring non-curly scope closer for $stackPtr:$type".PHP_EOL;
1017 }
1018 } else {
1019 $scopeCloser = $i;
1020 $todo = [
1021 $stackPtr,
1022 $opener,
1023 ];
1024
1025 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1026 $type = $this->tokens[$stackPtr]['type'];
1027 $closerType = $this->tokens[$scopeCloser]['type'];
1028 echo str_repeat("\t", $depth);
1029 echo "=> Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type".PHP_EOL;
1030 }
1031
1032 $validCloser = true;
1033 if (($this->tokens[$stackPtr]['code'] === T_IF || $this->tokens[$stackPtr]['code'] === T_ELSEIF)
1034 && ($tokenType === T_ELSE || $tokenType === T_ELSEIF)
1035 ) {
1036 // To be a closer, this token must have an opener.
1037 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1038 echo str_repeat("\t", $depth);
1039 echo "* closer needs to be tested *".PHP_EOL;
1040 }
1041
1042 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
1043
1044 if (isset($this->tokens[$scopeCloser]['scope_opener']) === false) {
1045 $validCloser = false;
1046 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1047 echo str_repeat("\t", $depth);
1048 echo "* closer is not valid (no opener found) *".PHP_EOL;
1049 }
1050 } else if ($this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['code'] !== $this->tokens[$opener]['code']) {
1051 $validCloser = false;
1052 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1053 echo str_repeat("\t", $depth);
1054 $type = $this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['type'];
1055 $openerType = $this->tokens[$opener]['type'];
1056 echo "* closer is not valid (mismatched opener type; $type != $openerType) *".PHP_EOL;
1057 }
1058 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
1059 echo str_repeat("\t", $depth);
1060 echo "* closer was valid *".PHP_EOL;
1061 }
1062 } else {
1063 // The closer was not processed, so we need to
1064 // complete that token as well.
1065 $todo[] = $scopeCloser;
1066 }//end if
1067
1068 if ($validCloser === true) {
1069 foreach ($todo as $token) {
1070 $this->tokens[$token]['scope_condition'] = $stackPtr;
1071 $this->tokens[$token]['scope_opener'] = $opener;
1072 $this->tokens[$token]['scope_closer'] = $scopeCloser;
1073 }
1074
1075 if ($this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true) {
1076 // As we are going back to where we started originally, restore
1077 // the ignore value back to its original value.
1078 $ignore = $originalIgnore;
1079 return $opener;
1080 } else if ($scopeCloser === $i
1081 && isset($this->scopeOpeners[$tokenType]) === true
1082 ) {
1083 // Unset scope_condition here or else the token will appear to have
1084 // already been processed, and it will be skipped. Normally we want that,
1085 // but in this case, the token is both a closer and an opener, so
1086 // it needs to act like an opener. This is also why we return the
1087 // token before this one; so the closer has a chance to be processed
1088 // a second time, but as an opener.
1089 unset($this->tokens[$scopeCloser]['scope_condition']);
1090 return ($i - 1);
1091 } else {
1092 return $i;
1093 }
1094 } else {
1095 continue;
1096 }//end if
1097 }//end if
1098 }//end if
1099
1100 // Is this an opening condition ?
1101 if (isset($this->scopeOpeners[$tokenType]) === true) {
1102 if ($opener === null) {
1103 if ($tokenType === T_USE) {
1104 // PHP use keywords are special because they can be
1105 // used as blocks but also inline in function definitions.
1106 // So if we find them nested inside another opener, just skip them.
1107 continue;
1108 }
1109
1110 if ($tokenType === T_FUNCTION
1111 && $this->tokens[$stackPtr]['code'] !== T_FUNCTION
1112 ) {
1113 // Probably a closure, so process it manually.
1114 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1115 $type = $this->tokens[$stackPtr]['type'];
1116 echo str_repeat("\t", $depth);
1117 echo "=> Found function before scope opener for $stackPtr:$type, processing manually".PHP_EOL;
1118 }
1119
1120 if (isset($this->tokens[$i]['scope_closer']) === true) {
1121 // We've already processed this closure.
1122 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1123 echo str_repeat("\t", $depth);
1124 echo '* already processed, skipping *'.PHP_EOL;
1125 }
1126
1127 $i = $this->tokens[$i]['scope_closer'];
1128 continue;
1129 }
1130
1131 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
1132 continue;
1133 }//end if
1134
1135 if ($tokenType === T_CLASS) {
1136 // Probably an anonymous class inside another anonymous class,
1137 // so process it manually.
1138 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1139 $type = $this->tokens[$stackPtr]['type'];
1140 echo str_repeat("\t", $depth);
1141 echo "=> Found class before scope opener for $stackPtr:$type, processing manually".PHP_EOL;
1142 }
1143
1144 if (isset($this->tokens[$i]['scope_closer']) === true) {
1145 // We've already processed this anon class.
1146 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1147 echo str_repeat("\t", $depth);
1148 echo '* already processed, skipping *'.PHP_EOL;
1149 }
1150
1151 $i = $this->tokens[$i]['scope_closer'];
1152 continue;
1153 }
1154
1155 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
1156 continue;
1157 }//end if
1158
1159 // Found another opening condition but still haven't
1160 // found our opener, so we are never going to find one.
1161 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1162 $type = $this->tokens[$stackPtr]['type'];
1163 echo str_repeat("\t", $depth);
1164 echo "=> Found new opening condition before scope opener for $stackPtr:$type, ";
1165 }
1166
1167 if (($this->tokens[$stackPtr]['code'] === T_IF
1168 || $this->tokens[$stackPtr]['code'] === T_ELSEIF
1169 || $this->tokens[$stackPtr]['code'] === T_ELSE)
1170 && ($this->tokens[$i]['code'] === T_ELSE
1171 || $this->tokens[$i]['code'] === T_ELSEIF)
1172 ) {
1173 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1174 echo "continuing".PHP_EOL;
1175 }
1176
1177 return ($i - 1);
1178 } else {
1179 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1180 echo "backtracking".PHP_EOL;
1181 }
1182
1183 return $stackPtr;
1184 }
1185 }//end if
1186
1187 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1188 echo str_repeat("\t", $depth);
1189 echo '* token is an opening condition *'.PHP_EOL;
1190 }
1191
1192 $isShared = ($this->scopeOpeners[$tokenType]['shared'] === true);
1193
1194 if (isset($this->tokens[$i]['scope_condition']) === true) {
1195 // We've been here before.
1196 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1197 echo str_repeat("\t", $depth);
1198 echo '* already processed, skipping *'.PHP_EOL;
1199 }
1200
1201 if ($isShared === false
1202 && isset($this->tokens[$i]['scope_closer']) === true
1203 ) {
1204 $i = $this->tokens[$i]['scope_closer'];
1205 }
1206
1207 continue;
1208 } else if ($currType === $tokenType
1209 && $isShared === false
1210 && $opener === null
1211 ) {
1212 // We haven't yet found our opener, but we have found another
1213 // scope opener which is the same type as us, and we don't
1214 // share openers, so we will never find one.
1215 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1216 echo str_repeat("\t", $depth);
1217 echo '* it was another token\'s opener, bailing *'.PHP_EOL;
1218 }
1219
1220 return $stackPtr;
1221 } else {
1222 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1223 echo str_repeat("\t", $depth);
1224 echo '* searching for opener *'.PHP_EOL;
1225 }
1226
1227 if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
1228 $oldIgnore = $ignore;
1229 $ignore = 0;
1230 }
1231
1232 // PHP has a max nesting level for functions. Stop before we hit that limit
1233 // because too many loops means we've run into trouble anyway.
1234 if ($depth > 50) {
1235 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1236 echo str_repeat("\t", $depth);
1237 echo '* reached maximum nesting level; aborting *'.PHP_EOL;
1238 }
1239
1240 throw new TokenizerException('Maximum nesting level reached; file could not be processed');
1241 }
1242
1243 $oldDepth = $depth;
1244 if ($isShared === true
1245 && isset($this->scopeOpeners[$tokenType]['with'][$currType]) === true
1246 ) {
1247 // Don't allow the depth to increment because this is
1248 // possibly not a true nesting if we are sharing our closer.
1249 // This can happen, for example, when a SWITCH has a large
1250 // number of CASE statements with the same shared BREAK.
1251 $depth--;
1252 }
1253
1254 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
1255 $depth = $oldDepth;
1256
1257 if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
1258 $ignore = $oldIgnore;
1259 }
1260 }//end if
1261 }//end if
1262
1263 if (isset($this->scopeOpeners[$currType]['start'][$tokenType]) === true
1264 && $opener === null
1265 ) {
1266 if ($tokenType === T_OPEN_CURLY_BRACKET) {
1267 if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true
1268 && $i < $this->tokens[$stackPtr]['parenthesis_closer']
1269 ) {
1270 // We found a curly brace inside the condition of the
1271 // current scope opener, so it must be a string offset.
1272 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1273 echo str_repeat("\t", $depth);
1274 echo '* ignoring curly brace inside condition *'.PHP_EOL;
1275 }
1276
1277 $ignore++;
1278 } else {
1279 // Make sure this is actually an opener and not a
1280 // string offset (e.g., $var{0}).
1281 for ($x = ($i - 1); $x > 0; $x--) {
1282 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
1283 continue;
1284 } else {
1285 // If the first non-whitespace/comment token looks like this
1286 // brace is a string offset, or this brace is mid-way through
1287 // a new statement, it isn't a scope opener.
1288 $disallowed = Util\Tokens::$assignmentTokens;
1289 $disallowed += [
1290 T_DOLLAR => true,
1291 T_VARIABLE => true,
1292 T_OBJECT_OPERATOR => true,
1293 T_COMMA => true,
1294 T_OPEN_PARENTHESIS => true,
1295 ];
1296
1297 if (isset($disallowed[$this->tokens[$x]['code']]) === true) {
1298 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1299 echo str_repeat("\t", $depth);
1300 echo '* ignoring curly brace *'.PHP_EOL;
1301 }
1302
1303 $ignore++;
1304 }
1305
1306 break;
1307 }//end if
1308 }//end for
1309 }//end if
1310 }//end if
1311
1312 if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
1313 // We found the opening scope token for $currType.
1314 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1315 $type = $this->tokens[$stackPtr]['type'];
1316 echo str_repeat("\t", $depth);
1317 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
1318 }
1319
1320 $opener = $i;
1321 }
1322 } else if ($tokenType === T_SEMICOLON
1323 && $opener === null
1324 && (isset($this->tokens[$stackPtr]['parenthesis_closer']) === false
1325 || $i > $this->tokens[$stackPtr]['parenthesis_closer'])
1326 ) {
1327 // Found the end of a statement but still haven't
1328 // found our opener, so we are never going to find one.
1329 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1330 $type = $this->tokens[$stackPtr]['type'];
1331 echo str_repeat("\t", $depth);
1332 echo "=> Found end of statement before scope opener for $stackPtr:$type, continuing".PHP_EOL;
1333 }
1334
1335 return ($i - 1);
1336 } else if ($tokenType === T_OPEN_PARENTHESIS) {
1337 if (isset($this->tokens[$i]['parenthesis_owner']) === true) {
1338 $owner = $this->tokens[$i]['parenthesis_owner'];
1339 if (isset(Util\Tokens::$scopeOpeners[$this->tokens[$owner]['code']]) === true
1340 && isset($this->tokens[$i]['parenthesis_closer']) === true
1341 ) {
1342 // If we get into here, then we opened a parenthesis for
1343 // a scope (eg. an if or else if) so we need to update the
1344 // start of the line so that when we check to see
1345 // if the closing parenthesis is more than n lines away from
1346 // the statement, we check from the closing parenthesis.
1347 $startLine = $this->tokens[$this->tokens[$i]['parenthesis_closer']]['line'];
1348 }
1349 }
1350 } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
1351 // We opened something that we don't have a scope opener for.
1352 // Examples of this are curly brackets for string offsets etc.
1353 // We want to ignore this so that we don't have an invalid scope
1354 // map.
1355 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1356 echo str_repeat("\t", $depth);
1357 echo '* ignoring curly brace *'.PHP_EOL;
1358 }
1359
1360 $ignore++;
1361 } else if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) {
1362 // We found the end token for the opener we were ignoring.
1363 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1364 echo str_repeat("\t", $depth);
1365 echo '* finished ignoring curly brace *'.PHP_EOL;
1366 }
1367
1368 $ignore--;
1369 } else if ($opener === null
1370 && isset($this->scopeOpeners[$currType]) === true
1371 ) {
1372 // If we still haven't found the opener after 30 lines,
1373 // we're not going to find it, unless we know it requires
1374 // an opener (in which case we better keep looking) or the last
1375 // token was empty (in which case we'll just confirm there is
1376 // more code in this file and not just a big comment).
1377 if ($this->tokens[$i]['line'] >= ($startLine + 30)
1378 && isset(Util\Tokens::$emptyTokens[$this->tokens[($i - 1)]['code']]) === false
1379 ) {
1380 if ($this->scopeOpeners[$currType]['strict'] === true) {
1381 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1382 $type = $this->tokens[$stackPtr]['type'];
1383 $lines = ($this->tokens[$i]['line'] - $startLine);
1384 echo str_repeat("\t", $depth);
1385 echo "=> Still looking for $stackPtr:$type scope opener after $lines lines".PHP_EOL;
1386 }
1387 } else {
1388 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1389 $type = $this->tokens[$stackPtr]['type'];
1390 echo str_repeat("\t", $depth);
1391 echo "=> Couldn't find scope opener for $stackPtr:$type, bailing".PHP_EOL;
1392 }
1393
1394 return $stackPtr;
1395 }
1396 }
1397 } else if ($opener !== null
1398 && $tokenType !== T_BREAK
1399 && isset($this->endScopeTokens[$tokenType]) === true
1400 ) {
1401 if (isset($this->tokens[$i]['scope_condition']) === false) {
1402 if ($ignore > 0) {
1403 // We found the end token for the opener we were ignoring.
1404 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1405 echo str_repeat("\t", $depth);
1406 echo '* finished ignoring curly brace *'.PHP_EOL;
1407 }
1408
1409 $ignore--;
1410 } else {
1411 // We found a token that closes the scope but it doesn't
1412 // have a condition, so it belongs to another token and
1413 // our token doesn't have a closer, so pretend this is
1414 // the closer.
1415 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1416 $type = $this->tokens[$stackPtr]['type'];
1417 echo str_repeat("\t", $depth);
1418 echo "=> Found (unexpected) scope closer for $stackPtr:$type".PHP_EOL;
1419 }
1420
1421 foreach ([$stackPtr, $opener] as $token) {
1422 $this->tokens[$token]['scope_condition'] = $stackPtr;
1423 $this->tokens[$token]['scope_opener'] = $opener;
1424 $this->tokens[$token]['scope_closer'] = $i;
1425 }
1426
1427 return ($i - 1);
1428 }//end if
1429 }//end if
1430 }//end if
1431 }//end for
1432
1433 return $stackPtr;
1434
1435 }//end recurseScopeMap()
1436
1437
1438 /**
1439 * Constructs the level map.
1440 *
1441 * The level map adds a 'level' index to each token which indicates the
1442 * depth that a token within a set of scope blocks. It also adds a
1443 * 'conditions' index which is an array of the scope conditions that opened
1444 * each of the scopes - position 0 being the first scope opener.
1445 *
1446 * @return void
1447 */
1448 private function createLevelMap()
1449 {
1450 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1451 echo "\t*** START LEVEL MAP ***".PHP_EOL;
1452 }
1453
1454 $this->numTokens = count($this->tokens);
1455 $level = 0;
1456 $conditions = [];
1457 $lastOpener = null;
1458 $openers = [];
1459
1460 for ($i = 0; $i < $this->numTokens; $i++) {
1461 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1462 $type = $this->tokens[$i]['type'];
1463 $line = $this->tokens[$i]['line'];
1464 $len = $this->tokens[$i]['length'];
1465 $col = $this->tokens[$i]['column'];
1466
1467 $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
1468
1469 echo str_repeat("\t", ($level + 1));
1470 echo "Process token $i on line $line [col:$col;len:$len;lvl:$level;";
1471 if (empty($conditions) !== true) {
1472 $conditionString = 'conds;';
1473 foreach ($conditions as $condition) {
1474 $conditionString .= Util\Tokens::tokenName($condition).',';
1475 }
1476
1477 echo rtrim($conditionString, ',').';';
1478 }
1479
1480 echo "]: $type => $content".PHP_EOL;
1481 }//end if
1482
1483 $this->tokens[$i]['level'] = $level;
1484 $this->tokens[$i]['conditions'] = $conditions;
1485
1486 if (isset($this->tokens[$i]['scope_condition']) === true) {
1487 // Check to see if this token opened the scope.
1488 if ($this->tokens[$i]['scope_opener'] === $i) {
1489 $stackPtr = $this->tokens[$i]['scope_condition'];
1490 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1491 $type = $this->tokens[$stackPtr]['type'];
1492 echo str_repeat("\t", ($level + 1));
1493 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
1494 }
1495
1496 $stackPtr = $this->tokens[$i]['scope_condition'];
1497
1498 // If we find a scope opener that has a shared closer,
1499 // then we need to go back over the condition map that we
1500 // just created and fix ourselves as we just added some
1501 // conditions where there was none. This happens for T_CASE
1502 // statements that are using the same break statement.
1503 if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $this->tokens[$i]['scope_closer']) {
1504 // This opener shares its closer with the previous opener,
1505 // but we still need to check if the two openers share their
1506 // closer with each other directly (like CASE and DEFAULT)
1507 // or if they are just sharing because one doesn't have a
1508 // closer (like CASE with no BREAK using a SWITCHes closer).
1509 $thisType = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
1510 $opener = $this->tokens[$lastOpener]['scope_condition'];
1511
1512 $isShared = isset($this->scopeOpeners[$thisType]['with'][$this->tokens[$opener]['code']]);
1513
1514 reset($this->scopeOpeners[$thisType]['end']);
1515 reset($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
1516 $sameEnd = (current($this->scopeOpeners[$thisType]['end']) === current($this->scopeOpeners[$this->tokens[$opener]['code']]['end']));
1517
1518 if ($isShared === true && $sameEnd === true) {
1519 $badToken = $opener;
1520 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1521 $type = $this->tokens[$badToken]['type'];
1522 echo str_repeat("\t", ($level + 1));
1523 echo "* shared closer, cleaning up $badToken:$type *".PHP_EOL;
1524 }
1525
1526 for ($x = $this->tokens[$i]['scope_condition']; $x <= $i; $x++) {
1527 $oldConditions = $this->tokens[$x]['conditions'];
1528 $oldLevel = $this->tokens[$x]['level'];
1529 $this->tokens[$x]['level']--;
1530 unset($this->tokens[$x]['conditions'][$badToken]);
1531 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1532 $type = $this->tokens[$x]['type'];
1533 $oldConds = '';
1534 foreach ($oldConditions as $condition) {
1535 $oldConds .= Util\Tokens::tokenName($condition).',';
1536 }
1537
1538 $oldConds = rtrim($oldConds, ',');
1539
1540 $newConds = '';
1541 foreach ($this->tokens[$x]['conditions'] as $condition) {
1542 $newConds .= Util\Tokens::tokenName($condition).',';
1543 }
1544
1545 $newConds = rtrim($newConds, ',');
1546
1547 $newLevel = $this->tokens[$x]['level'];
1548 echo str_repeat("\t", ($level + 1));
1549 echo "* cleaned $x:$type *".PHP_EOL;
1550 echo str_repeat("\t", ($level + 2));
1551 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
1552 echo str_repeat("\t", ($level + 2));
1553 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
1554 }//end if
1555 }//end for
1556
1557 unset($conditions[$badToken]);
1558 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1559 $type = $this->tokens[$badToken]['type'];
1560 echo str_repeat("\t", ($level + 1));
1561 echo "* token $badToken:$type removed from conditions array *".PHP_EOL;
1562 }
1563
1564 unset($openers[$lastOpener]);
1565
1566 $level--;
1567 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1568 echo str_repeat("\t", ($level + 2));
1569 echo '* level decreased *'.PHP_EOL;
1570 }
1571 }//end if
1572 }//end if
1573
1574 $level++;
1575 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1576 echo str_repeat("\t", ($level + 1));
1577 echo '* level increased *'.PHP_EOL;
1578 }
1579
1580 $conditions[$stackPtr] = $this->tokens[$stackPtr]['code'];
1581 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1582 $type = $this->tokens[$stackPtr]['type'];
1583 echo str_repeat("\t", ($level + 1));
1584 echo "* token $stackPtr:$type added to conditions array *".PHP_EOL;
1585 }
1586
1587 $lastOpener = $this->tokens[$i]['scope_opener'];
1588 if ($lastOpener !== null) {
1589 $openers[$lastOpener] = $lastOpener;
1590 }
1591 } else if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $i) {
1592 foreach (array_reverse($openers) as $opener) {
1593 if ($this->tokens[$opener]['scope_closer'] === $i) {
1594 $oldOpener = array_pop($openers);
1595 if (empty($openers) === false) {
1596 $lastOpener = array_pop($openers);
1597 $openers[$lastOpener] = $lastOpener;
1598 } else {
1599 $lastOpener = null;
1600 }
1601
1602 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1603 $type = $this->tokens[$oldOpener]['type'];
1604 echo str_repeat("\t", ($level + 1));
1605 echo "=> Found scope closer for $oldOpener:$type".PHP_EOL;
1606 }
1607
1608 $oldCondition = array_pop($conditions);
1609 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1610 echo str_repeat("\t", ($level + 1));
1611 echo '* token '.Util\Tokens::tokenName($oldCondition).' removed from conditions array *'.PHP_EOL;
1612 }
1613
1614 // Make sure this closer actually belongs to us.
1615 // Either the condition also has to think this is the
1616 // closer, or it has to allow sharing with us.
1617 $condition = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
1618 if ($condition !== $oldCondition) {
1619 if (isset($this->scopeOpeners[$oldCondition]['with'][$condition]) === false) {
1620 $badToken = $this->tokens[$oldOpener]['scope_condition'];
1621
1622 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1623 $type = Util\Tokens::tokenName($oldCondition);
1624 echo str_repeat("\t", ($level + 1));
1625 echo "* scope closer was bad, cleaning up $badToken:$type *".PHP_EOL;
1626 }
1627
1628 for ($x = ($oldOpener + 1); $x <= $i; $x++) {
1629 $oldConditions = $this->tokens[$x]['conditions'];
1630 $oldLevel = $this->tokens[$x]['level'];
1631 $this->tokens[$x]['level']--;
1632 unset($this->tokens[$x]['conditions'][$badToken]);
1633 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1634 $type = $this->tokens[$x]['type'];
1635 $oldConds = '';
1636 foreach ($oldConditions as $condition) {
1637 $oldConds .= Util\Tokens::tokenName($condition).',';
1638 }
1639
1640 $oldConds = rtrim($oldConds, ',');
1641
1642 $newConds = '';
1643 foreach ($this->tokens[$x]['conditions'] as $condition) {
1644 $newConds .= Util\Tokens::tokenName($condition).',';
1645 }
1646
1647 $newConds = rtrim($newConds, ',');
1648
1649 $newLevel = $this->tokens[$x]['level'];
1650 echo str_repeat("\t", ($level + 1));
1651 echo "* cleaned $x:$type *".PHP_EOL;
1652 echo str_repeat("\t", ($level + 2));
1653 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
1654 echo str_repeat("\t", ($level + 2));
1655 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
1656 }//end if
1657 }//end for
1658 }//end if
1659 }//end if
1660
1661 $level--;
1662 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1663 echo str_repeat("\t", ($level + 2));
1664 echo '* level decreased *'.PHP_EOL;
1665 }
1666
1667 $this->tokens[$i]['level'] = $level;
1668 $this->tokens[$i]['conditions'] = $conditions;
1669 }//end if
1670 }//end foreach
1671 }//end if
1672 }//end if
1673 }//end for
1674
1675 if (PHP_CODESNIFFER_VERBOSITY > 1) {
1676 echo "\t*** END LEVEL MAP ***".PHP_EOL;
1677 }
1678
1679 }//end createLevelMap()
1680
1681
1682 }//end class
1683