annotate vendor/squizlabs/php_codesniffer/src/Tokenizers/Tokenizer.php @ 17:129ea1e6d783

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