annotate vendor/squizlabs/php_codesniffer/src/Tokenizers/Tokenizer.php @ 5:12f9dff5fda9 tip

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