annotate vendor/squizlabs/php_codesniffer/src/Tokenizers/Tokenizer.php @ 19:fa3358dc1485 tip

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