1<?php 2/** 3 * A PHP_CodeSniffer_File object represents a PHP source file and the tokens 4 * associated with it. 5 * 6 * PHP version 5 7 * 8 * @category PHP 9 * @package PHP_CodeSniffer 10 * @author Greg Sherwood <gsherwood@squiz.net> 11 * @author Marc McIntyre <mmcintyre@squiz.net> 12 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 13 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 14 * @link http://pear.php.net/package/PHP_CodeSniffer 15 */ 16 17/** 18 * A PHP_CodeSniffer_File object represents a PHP source file and the tokens 19 * associated with it. 20 * 21 * It provides a means for traversing the token stack, along with 22 * other token related operations. If a PHP_CodeSniffer_Sniff finds and error or 23 * warning within a PHP_CodeSniffer_File, you can raise an error using the 24 * addError() or addWarning() methods. 25 * 26 * <b>Token Information</b> 27 * 28 * Each token within the stack contains information about itself: 29 * 30 * <code> 31 * array( 32 * 'code' => 301, // the token type code (see token_get_all()) 33 * 'content' => 'if', // the token content 34 * 'type' => 'T_IF', // the token name 35 * 'line' => 56, // the line number when the token is located 36 * 'column' => 12, // the column in the line where this token 37 * // starts (starts from 1) 38 * 'level' => 2 // the depth a token is within the scopes open 39 * 'conditions' => array( // a list of scope condition token 40 * // positions => codes that 41 * 2 => 50, // opened the scopes that this token exists 42 * 9 => 353, // in (see conditional tokens section below) 43 * ), 44 * ); 45 * </code> 46 * 47 * <b>Conditional Tokens</b> 48 * 49 * In addition to the standard token fields, conditions contain information to 50 * determine where their scope begins and ends: 51 * 52 * <code> 53 * array( 54 * 'scope_condition' => 38, // the token position of the condition 55 * 'scope_opener' => 41, // the token position that started the scope 56 * 'scope_closer' => 70, // the token position that ended the scope 57 * ); 58 * </code> 59 * 60 * The condition, the scope opener and the scope closer each contain this 61 * information. 62 * 63 * <b>Parenthesis Tokens</b> 64 * 65 * Each parenthesis token (T_OPEN_PARENTHESIS and T_CLOSE_PARENTHESIS) has a 66 * reference to their opening and closing parenthesis, one being itself, the 67 * other being its opposite. 68 * 69 * <code> 70 * array( 71 * 'parenthesis_opener' => 34, 72 * 'parenthesis_closer' => 40, 73 * ); 74 * </code> 75 * 76 * Some tokens can "own" a set of parenthesis. For example a T_FUNCTION token 77 * has parenthesis around its argument list. These tokens also have the 78 * parenthesis_opener and and parenthesis_closer indices. Not all parenthesis 79 * have owners, for example parenthesis used for arithmetic operations and 80 * function calls. The parenthesis tokens that have an owner have the following 81 * auxiliary array indices. 82 * 83 * <code> 84 * array( 85 * 'parenthesis_opener' => 34, 86 * 'parenthesis_closer' => 40, 87 * 'parenthesis_owner' => 33, 88 * ); 89 * </code> 90 * 91 * Each token within a set of parenthesis also has an array index 92 * 'nested_parenthesis' which is an array of the 93 * left parenthesis => right parenthesis token positions. 94 * 95 * <code> 96 * 'nested_parenthesis' => array( 97 * 12 => 15 98 * 11 => 14 99 * ); 100 * </code> 101 * 102 * <b>Extended Tokens</b> 103 * 104 * PHP_CodeSniffer extends and augments some of the tokens created by 105 * <i>token_get_all()</i>. A full list of these tokens can be seen in the 106 * <i>Tokens.php</i> file. 107 * 108 * @category PHP 109 * @package PHP_CodeSniffer 110 * @author Greg Sherwood <gsherwood@squiz.net> 111 * @author Marc McIntyre <mmcintyre@squiz.net> 112 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 113 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 114 * @version Release: @package_version@ 115 * @link http://pear.php.net/package/PHP_CodeSniffer 116 */ 117class PHP_CodeSniffer_File 118{ 119 120 /** 121 * The absolute path to the file associated with this object. 122 * 123 * @var string 124 */ 125 private $_file = ''; 126 127 /** 128 * The EOL character this file uses. 129 * 130 * @var string 131 */ 132 public $eolChar = ''; 133 134 /** 135 * The PHP_CodeSniffer object controlling this run. 136 * 137 * @var PHP_CodeSniffer 138 */ 139 public $phpcs = null; 140 141 /** 142 * The Fixer object to control fixing errors. 143 * 144 * @var PHP_CodeSniffer_Fixer 145 */ 146 public $fixer = null; 147 148 /** 149 * The tokenizer being used for this file. 150 * 151 * @var object 152 */ 153 public $tokenizer = null; 154 155 /** 156 * The tokenizer being used for this file. 157 * 158 * @var string 159 */ 160 public $tokenizerType = 'PHP'; 161 162 /** 163 * The number of tokens in this file. 164 * 165 * Stored here to save calling count() everywhere. 166 * 167 * @var int 168 */ 169 public $numTokens = 0; 170 171 /** 172 * The tokens stack map. 173 * 174 * Note that the tokens in this array differ in format to the tokens 175 * produced by token_get_all(). Tokens are initially produced with 176 * token_get_all(), then augmented so that it's easier to process them. 177 * 178 * @var array() 179 * @see Tokens.php 180 */ 181 private $_tokens = array(); 182 183 /** 184 * The errors raised from PHP_CodeSniffer_Sniffs. 185 * 186 * @var array() 187 * @see getErrors() 188 */ 189 private $_errors = array(); 190 191 /** 192 * The warnings raised from PHP_CodeSniffer_Sniffs. 193 * 194 * @var array() 195 * @see getWarnings() 196 */ 197 private $_warnings = array(); 198 199 /** 200 * The metrics recorded from PHP_CodeSniffer_Sniffs. 201 * 202 * @var array() 203 * @see getMetrics() 204 */ 205 private $_metrics = array(); 206 207 /** 208 * Record the errors and warnings raised. 209 * 210 * @var bool 211 */ 212 private $_recordErrors = true; 213 214 /** 215 * An array of lines that are being ignored. 216 * 217 * @var array() 218 */ 219 private static $_ignoredLines = array(); 220 221 /** 222 * An array of sniffs that are being ignored. 223 * 224 * @var array() 225 */ 226 private $_ignoredListeners = array(); 227 228 /** 229 * An array of message codes that are being ignored. 230 * 231 * @var array() 232 */ 233 private $_ignoredCodes = array(); 234 235 /** 236 * The total number of errors raised. 237 * 238 * @var int 239 */ 240 private $_errorCount = 0; 241 242 /** 243 * The total number of warnings raised. 244 * 245 * @var int 246 */ 247 private $_warningCount = 0; 248 249 /** 250 * The total number of errors/warnings that can be fixed. 251 * 252 * @var int 253 */ 254 private $_fixableCount = 0; 255 256 /** 257 * An array of sniffs listening to this file's processing. 258 * 259 * @var array(PHP_CodeSniffer_Sniff) 260 */ 261 private $_listeners = array(); 262 263 /** 264 * The class name of the sniff currently processing the file. 265 * 266 * @var string 267 */ 268 private $_activeListener = ''; 269 270 /** 271 * An array of sniffs being processed and how long they took. 272 * 273 * @var array() 274 */ 275 private $_listenerTimes = array(); 276 277 /** 278 * An array of rules from the ruleset.xml file. 279 * 280 * This value gets set by PHP_CodeSniffer when the object is created. 281 * It may be empty, indicating that the ruleset does not override 282 * any of the default sniff settings. 283 * 284 * @var array 285 */ 286 protected $ruleset = array(); 287 288 289 /** 290 * Constructs a PHP_CodeSniffer_File. 291 * 292 * @param string $file The absolute path to the file to process. 293 * @param array(string) $listeners The initial listeners listening to processing of this file. 294 * to processing of this file. 295 * @param array $ruleset An array of rules from the ruleset.xml file. 296 * ruleset.xml file. 297 * @param PHP_CodeSniffer $phpcs The PHP_CodeSniffer object controlling this run. 298 * this run. 299 * 300 * @throws PHP_CodeSniffer_Exception If the register() method does 301 * not return an array. 302 */ 303 public function __construct( 304 $file, 305 array $listeners, 306 array $ruleset, 307 PHP_CodeSniffer $phpcs 308 ) { 309 $this->_file = trim($file); 310 $this->_listeners = $listeners; 311 $this->ruleset = $ruleset; 312 $this->phpcs = $phpcs; 313 $this->fixer = new PHP_CodeSniffer_Fixer(); 314 315 if (PHP_CODESNIFFER_INTERACTIVE === false) { 316 $cliValues = $phpcs->cli->getCommandLineValues(); 317 if (isset($cliValues['showSources']) === true 318 && $cliValues['showSources'] !== true 319 ) { 320 $recordErrors = false; 321 foreach ($cliValues['reports'] as $report => $output) { 322 $reportClass = $phpcs->reporting->factory($report); 323 if (property_exists($reportClass, 'recordErrors') === false 324 || $reportClass->recordErrors === true 325 ) { 326 $recordErrors = true; 327 break; 328 } 329 } 330 331 $this->_recordErrors = $recordErrors; 332 } 333 } 334 335 }//end __construct() 336 337 338 /** 339 * Sets the name of the currently active sniff. 340 * 341 * @param string $activeListener The class name of the current sniff. 342 * 343 * @return void 344 */ 345 public function setActiveListener($activeListener) 346 { 347 $this->_activeListener = $activeListener; 348 349 }//end setActiveListener() 350 351 352 /** 353 * Adds a listener to the token stack that listens to the specific tokens. 354 * 355 * When PHP_CodeSniffer encounters on the the tokens specified in $tokens, 356 * it invokes the process method of the sniff. 357 * 358 * @param PHP_CodeSniffer_Sniff $listener The listener to add to the 359 * listener stack. 360 * @param array(int) $tokens The token types the listener wishes to 361 * listen to. 362 * 363 * @return void 364 */ 365 public function addTokenListener(PHP_CodeSniffer_Sniff $listener, array $tokens) 366 { 367 $class = get_class($listener); 368 foreach ($tokens as $token) { 369 if (isset($this->_listeners[$token]) === false) { 370 $this->_listeners[$token] = array(); 371 } 372 373 if (isset($this->_listeners[$token][$class]) === false) { 374 $this->_listeners[$token][$class] = $listener; 375 } 376 } 377 378 }//end addTokenListener() 379 380 381 /** 382 * Removes a listener from listening from the specified tokens. 383 * 384 * @param PHP_CodeSniffer_Sniff $listener The listener to remove from the 385 * listener stack. 386 * @param array(int) $tokens The token types the listener wishes to 387 * stop listen to. 388 * 389 * @return void 390 */ 391 public function removeTokenListener( 392 PHP_CodeSniffer_Sniff $listener, 393 array $tokens 394 ) { 395 $class = get_class($listener); 396 foreach ($tokens as $token) { 397 if (isset($this->_listeners[$token]) === false) { 398 continue; 399 } 400 401 unset($this->_listeners[$token][$class]); 402 } 403 404 }//end removeTokenListener() 405 406 407 /** 408 * Rebuilds the list of listeners to ensure their state is cleared. 409 * 410 * @return void 411 */ 412 public function refreshTokenListeners() 413 { 414 $this->phpcs->populateTokenListeners(); 415 $this->_listeners = $this->phpcs->getTokenSniffs(); 416 417 }//end refreshTokenListeners() 418 419 420 /** 421 * Returns the token stack for this file. 422 * 423 * @return array 424 */ 425 public function getTokens() 426 { 427 return $this->_tokens; 428 429 }//end getTokens() 430 431 432 /** 433 * Starts the stack traversal and tells listeners when tokens are found. 434 * 435 * @param string $contents The contents to parse. If NULL, the content 436 * is taken from the file system. 437 * 438 * @return void 439 */ 440 public function start($contents=null) 441 { 442 $this->_errors = array(); 443 $this->_warnings = array(); 444 $this->_errorCount = 0; 445 $this->_warningCount = 0; 446 $this->_fixableCount = 0; 447 448 // Reset the ignored lines because lines numbers may have changed 449 // if we are fixing this file. 450 self::$_ignoredLines = array(); 451 452 try { 453 $this->eolChar = self::detectLineEndings($this->_file, $contents); 454 } catch (PHP_CodeSniffer_Exception $e) { 455 $this->addWarning($e->getMessage(), null, 'Internal.DetectLineEndings'); 456 return; 457 } 458 459 // If this is standard input, see if a filename was passed in as well. 460 // This is done by including: phpcs_input_file: [file path] 461 // as the first line of content. 462 if ($this->_file === 'STDIN') { 463 $cliValues = $this->phpcs->cli->getCommandLineValues(); 464 if ($cliValues['stdinPath'] !== '') { 465 $this->_file = $cliValues['stdinPath']; 466 } else if ($contents !== null && substr($contents, 0, 17) === 'phpcs_input_file:') { 467 $eolPos = strpos($contents, $this->eolChar); 468 $filename = trim(substr($contents, 17, ($eolPos - 17))); 469 $contents = substr($contents, ($eolPos + strlen($this->eolChar))); 470 $this->_file = $filename; 471 } 472 } 473 474 $this->_parse($contents); 475 $this->fixer->startFile($this); 476 477 if (PHP_CODESNIFFER_VERBOSITY > 2) { 478 echo "\t*** START TOKEN PROCESSING ***".PHP_EOL; 479 } 480 481 $foundCode = false; 482 $listeners = $this->phpcs->getSniffs(); 483 $listenerIgnoreTo = array(); 484 $inTests = defined('PHP_CODESNIFFER_IN_TESTS'); 485 486 // Foreach of the listeners that have registered to listen for this 487 // token, get them to process it. 488 foreach ($this->_tokens as $stackPtr => $token) { 489 // Check for ignored lines. 490 if ($token['code'] === T_COMMENT 491 || $token['code'] === T_DOC_COMMENT_TAG 492 || ($inTests === true && $token['code'] === T_INLINE_HTML) 493 ) { 494 if (strpos($token['content'], '@codingStandards') !== false) { 495 if (strpos($token['content'], '@codingStandardsIgnoreFile') !== false) { 496 // Ignoring the whole file, just a little late. 497 $this->_errors = array(); 498 $this->_warnings = array(); 499 $this->_errorCount = 0; 500 $this->_warningCount = 0; 501 $this->_fixableCount = 0; 502 return; 503 } else if (strpos($token['content'], '@codingStandardsChangeSetting') !== false) { 504 $start = strpos($token['content'], '@codingStandardsChangeSetting'); 505 $comment = substr($token['content'], ($start + 30)); 506 $parts = explode(' ', $comment); 507 if (count($parts) >= 3 508 && isset($this->phpcs->sniffCodes[$parts[0]]) === true 509 ) { 510 $listenerCode = array_shift($parts); 511 $propertyCode = array_shift($parts); 512 $propertyValue = rtrim(implode(' ', $parts), " */\r\n"); 513 $listenerClass = $this->phpcs->sniffCodes[$listenerCode]; 514 $this->phpcs->setSniffProperty($listenerClass, $propertyCode, $propertyValue); 515 } 516 }//end if 517 }//end if 518 }//end if 519 520 if (PHP_CODESNIFFER_VERBOSITY > 2) { 521 $type = $token['type']; 522 $content = PHP_CodeSniffer::prepareForOutput($token['content']); 523 echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL; 524 } 525 526 if ($token['code'] !== T_INLINE_HTML) { 527 $foundCode = true; 528 } 529 530 if (isset($this->_listeners[$token['code']]) === false) { 531 continue; 532 } 533 534 foreach ($this->_listeners[$token['code']] as $listenerData) { 535 if (isset($this->_ignoredListeners[$listenerData['class']]) === true 536 || (isset($listenerIgnoreTo[$listenerData['class']]) === true 537 && $listenerIgnoreTo[$listenerData['class']] > $stackPtr) 538 ) { 539 // This sniff is ignoring past this token, or the whole file. 540 continue; 541 } 542 543 // Make sure this sniff supports the tokenizer 544 // we are currently using. 545 $class = $listenerData['class']; 546 547 if (isset($listenerData['tokenizers'][$this->tokenizerType]) === false) { 548 continue; 549 } 550 551 // If the file path matches one of our ignore patterns, skip it. 552 // While there is support for a type of each pattern 553 // (absolute or relative) we don't actually support it here. 554 foreach ($listenerData['ignore'] as $pattern) { 555 // We assume a / directory separator, as do the exclude rules 556 // most developers write, so we need a special case for any system 557 // that is different. 558 if (DIRECTORY_SEPARATOR === '\\') { 559 $pattern = str_replace('/', '\\\\', $pattern); 560 } 561 562 $pattern = '`'.$pattern.'`i'; 563 if (preg_match($pattern, $this->_file) === 1) { 564 $this->_ignoredListeners[$class] = true; 565 continue(2); 566 } 567 } 568 569 $this->_activeListener = $class; 570 571 if (PHP_CODESNIFFER_VERBOSITY > 2) { 572 $startTime = microtime(true); 573 echo "\t\t\tProcessing ".$this->_activeListener.'... '; 574 } 575 576 $ignoreTo = $listeners[$class]->process($this, $stackPtr); 577 if ($ignoreTo !== null) { 578 $listenerIgnoreTo[$this->_activeListener] = $ignoreTo; 579 } 580 581 if (PHP_CODESNIFFER_VERBOSITY > 2) { 582 $timeTaken = (microtime(true) - $startTime); 583 if (isset($this->_listenerTimes[$this->_activeListener]) === false) { 584 $this->_listenerTimes[$this->_activeListener] = 0; 585 } 586 587 $this->_listenerTimes[$this->_activeListener] += $timeTaken; 588 589 $timeTaken = round(($timeTaken), 4); 590 echo "DONE in $timeTaken seconds".PHP_EOL; 591 } 592 593 $this->_activeListener = ''; 594 }//end foreach 595 }//end foreach 596 597 if ($this->_recordErrors === false) { 598 $this->_errors = array(); 599 $this->_warnings = array(); 600 } 601 602 // If short open tags are off but the file being checked uses 603 // short open tags, the whole content will be inline HTML 604 // and nothing will be checked. So try and handle this case. 605 // We don't show this error for STDIN because we can't be sure the content 606 // actually came directly from the user. It could be something like 607 // refs from a Git pre-push hook. 608 if ($foundCode === false && $this->tokenizerType === 'PHP' && $this->_file !== 'STDIN') { 609 $shortTags = (bool) ini_get('short_open_tag'); 610 if ($shortTags === false) { 611 $error = 'No PHP code was found in this file and short open tags are not allowed by this install of PHP. This file may be using short open tags but PHP does not allow them.'; 612 $this->addWarning($error, null, 'Internal.NoCodeFound'); 613 } 614 } 615 616 if (PHP_CODESNIFFER_VERBOSITY > 2) { 617 echo "\t*** END TOKEN PROCESSING ***".PHP_EOL; 618 echo "\t*** START SNIFF PROCESSING REPORT ***".PHP_EOL; 619 620 asort($this->_listenerTimes, SORT_NUMERIC); 621 $this->_listenerTimes = array_reverse($this->_listenerTimes, true); 622 foreach ($this->_listenerTimes as $listener => $timeTaken) { 623 echo "\t$listener: ".round(($timeTaken), 4).' secs'.PHP_EOL; 624 } 625 626 echo "\t*** END SNIFF PROCESSING REPORT ***".PHP_EOL; 627 } 628 629 }//end start() 630 631 632 /** 633 * Remove vars stored in this file that are no longer required. 634 * 635 * @return void 636 */ 637 public function cleanUp() 638 { 639 $this->_tokens = null; 640 $this->_listeners = null; 641 642 }//end cleanUp() 643 644 645 /** 646 * Tokenizes the file and prepares it for the test run. 647 * 648 * @param string $contents The contents to parse. If NULL, the content 649 * is taken from the file system. 650 * 651 * @return void 652 */ 653 private function _parse($contents=null) 654 { 655 if ($contents === null && empty($this->_tokens) === false) { 656 // File has already been parsed. 657 return; 658 } 659 660 $stdin = false; 661 $cliValues = $this->phpcs->cli->getCommandLineValues(); 662 if (empty($cliValues['files']) === true) { 663 $stdin = true; 664 } 665 666 // Determine the tokenizer from the file extension. 667 $fileParts = explode('.', $this->_file); 668 $extension = array_pop($fileParts); 669 if (isset($this->phpcs->allowedFileExtensions[$extension]) === true) { 670 $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->phpcs->allowedFileExtensions[$extension]; 671 $this->tokenizerType = $this->phpcs->allowedFileExtensions[$extension]; 672 } else if (isset($this->phpcs->defaultFileExtensions[$extension]) === true) { 673 $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->phpcs->defaultFileExtensions[$extension]; 674 $this->tokenizerType = $this->phpcs->defaultFileExtensions[$extension]; 675 } else { 676 // Revert to default. 677 $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->tokenizerType; 678 } 679 680 $tokenizer = new $tokenizerClass(); 681 $this->tokenizer = $tokenizer; 682 683 if ($contents === null) { 684 $contents = file_get_contents($this->_file); 685 } 686 687 try { 688 $tabWidth = null; 689 $encoding = null; 690 if (defined('PHP_CODESNIFFER_IN_TESTS') === true) { 691 $cliValues = $this->phpcs->cli->getCommandLineValues(); 692 if (isset($cliValues['tabWidth']) === true) { 693 $tabWidth = $cliValues['tabWidth']; 694 } 695 696 if (isset($cliValues['encoding']) === true) { 697 $encoding = $cliValues['encoding']; 698 } 699 } 700 701 $this->_tokens = self::tokenizeString($contents, $tokenizer, $this->eolChar, $tabWidth, $encoding); 702 } catch (PHP_CodeSniffer_Exception $e) { 703 $this->addWarning($e->getMessage(), null, 'Internal.Tokenizer.Exception'); 704 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) { 705 echo "[$this->tokenizerType => tokenizer error]... "; 706 if (PHP_CODESNIFFER_VERBOSITY > 1) { 707 echo PHP_EOL; 708 } 709 } 710 711 return; 712 }//end try 713 714 $this->numTokens = count($this->_tokens); 715 716 // Check for mixed line endings as these can cause tokenizer errors and we 717 // should let the user know that the results they get may be incorrect. 718 // This is done by removing all backslashes, removing the newline char we 719 // detected, then converting newlines chars into text. If any backslashes 720 // are left at the end, we have additional newline chars in use. 721 $contents = str_replace('\\', '', $contents); 722 $contents = str_replace($this->eolChar, '', $contents); 723 $contents = str_replace("\n", '\n', $contents); 724 $contents = str_replace("\r", '\r', $contents); 725 if (strpos($contents, '\\') !== false) { 726 $error = 'File has mixed line endings; this may cause incorrect results'; 727 $this->addWarning($error, 0, 'Internal.LineEndings.Mixed'); 728 } 729 730 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) { 731 if ($this->numTokens === 0) { 732 $numLines = 0; 733 } else { 734 $numLines = $this->_tokens[($this->numTokens - 1)]['line']; 735 } 736 737 echo "[$this->tokenizerType => $this->numTokens tokens in $numLines lines]... "; 738 if (PHP_CODESNIFFER_VERBOSITY > 1) { 739 echo PHP_EOL; 740 } 741 } 742 743 }//end _parse() 744 745 746 /** 747 * Opens a file and detects the EOL character being used. 748 * 749 * @param string $file The full path to the file. 750 * @param string $contents The contents to parse. If NULL, the content 751 * is taken from the file system. 752 * 753 * @return string 754 * @throws PHP_CodeSniffer_Exception If $file could not be opened. 755 */ 756 public static function detectLineEndings($file, $contents=null) 757 { 758 if ($contents === null) { 759 // Determine the newline character being used in this file. 760 // Will be either \r, \r\n or \n. 761 if (is_readable($file) === false) { 762 $error = 'Error opening file; file no longer exists or you do not have access to read the file'; 763 throw new PHP_CodeSniffer_Exception($error); 764 } else { 765 $handle = fopen($file, 'r'); 766 if ($handle === false) { 767 $error = 'Error opening file; could not auto-detect line endings'; 768 throw new PHP_CodeSniffer_Exception($error); 769 } 770 } 771 772 $firstLine = fgets($handle); 773 fclose($handle); 774 775 $eolChar = substr($firstLine, -1); 776 if ($eolChar === "\n") { 777 $secondLastChar = substr($firstLine, -2, 1); 778 if ($secondLastChar === "\r") { 779 $eolChar = "\r\n"; 780 } 781 } else if ($eolChar !== "\r") { 782 // Must not be an EOL char at the end of the line. 783 // Probably a one-line file, so assume \n as it really 784 // doesn't matter considering there are no newlines. 785 $eolChar = "\n"; 786 } 787 } else { 788 if (preg_match("/\r\n?|\n/", $contents, $matches) !== 1) { 789 // Assuming there are no newlines. 790 $eolChar = "\n"; 791 } else { 792 $eolChar = $matches[0]; 793 } 794 }//end if 795 796 return $eolChar; 797 798 }//end detectLineEndings() 799 800 801 /** 802 * Records an error against a specific token in the file. 803 * 804 * @param string $error The error message. 805 * @param int $stackPtr The stack position where the error occurred. 806 * @param string $code A violation code unique to the sniff message. 807 * @param array $data Replacements for the error message. 808 * @param int $severity The severity level for this error. A value of 0 809 * will be converted into the default severity level. 810 * @param boolean $fixable Can the error be fixed by the sniff? 811 * 812 * @return boolean 813 */ 814 public function addError( 815 $error, 816 $stackPtr, 817 $code='', 818 $data=array(), 819 $severity=0, 820 $fixable=false 821 ) { 822 if ($stackPtr === null) { 823 $line = 1; 824 $column = 1; 825 } else { 826 $line = $this->_tokens[$stackPtr]['line']; 827 $column = $this->_tokens[$stackPtr]['column']; 828 } 829 830 return $this->_addError($error, $line, $column, $code, $data, $severity, $fixable); 831 832 }//end addError() 833 834 835 /** 836 * Records a warning against a specific token in the file. 837 * 838 * @param string $warning The error message. 839 * @param int $stackPtr The stack position where the error occurred. 840 * @param string $code A violation code unique to the sniff message. 841 * @param array $data Replacements for the warning message. 842 * @param int $severity The severity level for this warning. A value of 0 843 * will be converted into the default severity level. 844 * @param boolean $fixable Can the warning be fixed by the sniff? 845 * 846 * @return boolean 847 */ 848 public function addWarning( 849 $warning, 850 $stackPtr, 851 $code='', 852 $data=array(), 853 $severity=0, 854 $fixable=false 855 ) { 856 if ($stackPtr === null) { 857 $line = 1; 858 $column = 1; 859 } else { 860 $line = $this->_tokens[$stackPtr]['line']; 861 $column = $this->_tokens[$stackPtr]['column']; 862 } 863 864 return $this->_addWarning($warning, $line, $column, $code, $data, $severity, $fixable); 865 866 }//end addWarning() 867 868 869 /** 870 * Records an error against a specific line in the file. 871 * 872 * @param string $error The error message. 873 * @param int $line The line on which the error occurred. 874 * @param string $code A violation code unique to the sniff message. 875 * @param array $data Replacements for the error message. 876 * @param int $severity The severity level for this error. A value of 0 877 * will be converted into the default severity level. 878 * 879 * @return boolean 880 */ 881 public function addErrorOnLine( 882 $error, 883 $line, 884 $code='', 885 $data=array(), 886 $severity=0 887 ) { 888 return $this->_addError($error, $line, 1, $code, $data, $severity, false); 889 890 }//end addErrorOnLine() 891 892 893 /** 894 * Records a warning against a specific token in the file. 895 * 896 * @param string $warning The error message. 897 * @param int $line The line on which the warning occurred. 898 * @param string $code A violation code unique to the sniff message. 899 * @param array $data Replacements for the warning message. 900 * @param int $severity The severity level for this warning. A value of 0 901 * will be converted into the default severity level. 902 * 903 * @return boolean 904 */ 905 public function addWarningOnLine( 906 $warning, 907 $line, 908 $code='', 909 $data=array(), 910 $severity=0 911 ) { 912 return $this->_addWarning($warning, $line, 1, $code, $data, $severity, false); 913 914 }//end addWarningOnLine() 915 916 917 /** 918 * Records a fixable error against a specific token in the file. 919 * 920 * Returns true if the error was recorded and should be fixed. 921 * 922 * @param string $error The error message. 923 * @param int $stackPtr The stack position where the error occurred. 924 * @param string $code A violation code unique to the sniff message. 925 * @param array $data Replacements for the error message. 926 * @param int $severity The severity level for this error. A value of 0 927 * will be converted into the default severity level. 928 * 929 * @return boolean 930 */ 931 public function addFixableError( 932 $error, 933 $stackPtr, 934 $code='', 935 $data=array(), 936 $severity=0 937 ) { 938 $recorded = $this->addError($error, $stackPtr, $code, $data, $severity, true); 939 if ($recorded === true && $this->fixer->enabled === true) { 940 return true; 941 } 942 943 return false; 944 945 }//end addFixableError() 946 947 948 /** 949 * Records a fixable warning against a specific token in the file. 950 * 951 * Returns true if the warning was recorded and should be fixed. 952 * 953 * @param string $warning The error message. 954 * @param int $stackPtr The stack position where the error occurred. 955 * @param string $code A violation code unique to the sniff message. 956 * @param array $data Replacements for the warning message. 957 * @param int $severity The severity level for this warning. A value of 0 958 * will be converted into the default severity level. 959 * 960 * @return boolean 961 */ 962 public function addFixableWarning( 963 $warning, 964 $stackPtr, 965 $code='', 966 $data=array(), 967 $severity=0 968 ) { 969 $recorded = $this->addWarning($warning, $stackPtr, $code, $data, $severity, true); 970 if ($recorded === true && $this->fixer->enabled === true) { 971 return true; 972 } 973 974 return false; 975 976 }//end addFixableWarning() 977 978 979 /** 980 * Adds an error to the error stack. 981 * 982 * @param string $error The error message. 983 * @param int $line The line on which the error occurred. 984 * @param int $column The column at which the error occurred. 985 * @param string $code A violation code unique to the sniff message. 986 * @param array $data Replacements for the error message. 987 * @param int $severity The severity level for this error. A value of 0 988 * will be converted into the default severity level. 989 * @param boolean $fixable Can the error be fixed by the sniff? 990 * 991 * @return boolean 992 */ 993 private function _addError($error, $line, $column, $code, $data, $severity, $fixable) 994 { 995 if (isset(self::$_ignoredLines[$line]) === true) { 996 return false; 997 } 998 999 // Work out which sniff generated the error. 1000 if (substr($code, 0, 9) === 'Internal.') { 1001 // Any internal message. 1002 $sniffCode = $code; 1003 } else { 1004 $parts = explode('_', str_replace('\\', '_', $this->_activeListener)); 1005 if (isset($parts[3]) === true) { 1006 $sniff = $parts[0].'.'.$parts[2].'.'.$parts[3]; 1007 1008 // Remove "Sniff" from the end. 1009 $sniff = substr($sniff, 0, -5); 1010 } else { 1011 $sniff = 'unknownSniff'; 1012 } 1013 1014 $sniffCode = $sniff; 1015 if ($code !== '') { 1016 $sniffCode .= '.'.$code; 1017 } 1018 }//end if 1019 1020 // If we know this sniff code is being ignored for this file, return early. 1021 if (isset($this->_ignoredCodes[$sniffCode]) === true) { 1022 return false; 1023 } 1024 1025 // Make sure this message type has not been set to "warning". 1026 if (isset($this->ruleset[$sniffCode]['type']) === true 1027 && $this->ruleset[$sniffCode]['type'] === 'warning' 1028 ) { 1029 // Pass this off to the warning handler. 1030 return $this->_addWarning($error, $line, $column, $code, $data, $severity, $fixable); 1031 } else if ($this->phpcs->cli->errorSeverity === 0) { 1032 // Don't bother doing any processing as errors are just going to 1033 // be hidden in the reports anyway. 1034 return false; 1035 } 1036 1037 // Make sure we are interested in this severity level. 1038 if (isset($this->ruleset[$sniffCode]['severity']) === true) { 1039 $severity = $this->ruleset[$sniffCode]['severity']; 1040 } else if ($severity === 0) { 1041 $severity = PHPCS_DEFAULT_ERROR_SEV; 1042 } 1043 1044 if ($this->phpcs->cli->errorSeverity > $severity) { 1045 return false; 1046 } 1047 1048 // Make sure we are not ignoring this file. 1049 $patterns = $this->phpcs->getIgnorePatterns($sniffCode); 1050 foreach ($patterns as $pattern => $type) { 1051 // While there is support for a type of each pattern 1052 // (absolute or relative) we don't actually support it here. 1053 $replacements = array( 1054 '\\,' => ',', 1055 '*' => '.*', 1056 ); 1057 1058 // We assume a / directory separator, as do the exclude rules 1059 // most developers write, so we need a special case for any system 1060 // that is different. 1061 if (DIRECTORY_SEPARATOR === '\\') { 1062 $replacements['/'] = '\\\\'; 1063 } 1064 1065 $pattern = '`'.strtr($pattern, $replacements).'`i'; 1066 if (preg_match($pattern, $this->_file) === 1) { 1067 $this->_ignoredCodes[$sniffCode] = true; 1068 return false; 1069 } 1070 }//end foreach 1071 1072 $this->_errorCount++; 1073 if ($fixable === true) { 1074 $this->_fixableCount++; 1075 } 1076 1077 if ($this->_recordErrors === false) { 1078 if (isset($this->_errors[$line]) === false) { 1079 $this->_errors[$line] = 0; 1080 } 1081 1082 $this->_errors[$line]++; 1083 return true; 1084 } 1085 1086 // Work out the error message. 1087 if (isset($this->ruleset[$sniffCode]['message']) === true) { 1088 $error = $this->ruleset[$sniffCode]['message']; 1089 } 1090 1091 if (empty($data) === true) { 1092 $message = $error; 1093 } else { 1094 $message = vsprintf($error, $data); 1095 } 1096 1097 if (isset($this->_errors[$line]) === false) { 1098 $this->_errors[$line] = array(); 1099 } 1100 1101 if (isset($this->_errors[$line][$column]) === false) { 1102 $this->_errors[$line][$column] = array(); 1103 } 1104 1105 $this->_errors[$line][$column][] = array( 1106 'message' => $message, 1107 'source' => $sniffCode, 1108 'severity' => $severity, 1109 'fixable' => $fixable, 1110 ); 1111 1112 if (PHP_CODESNIFFER_VERBOSITY > 1 1113 && $this->fixer->enabled === true 1114 && $fixable === true 1115 ) { 1116 @ob_end_clean(); 1117 echo "\tE: [Line $line] $message ($sniffCode)".PHP_EOL; 1118 ob_start(); 1119 } 1120 1121 return true; 1122 1123 }//end _addError() 1124 1125 1126 /** 1127 * Adds an warning to the warning stack. 1128 * 1129 * @param string $warning The error message. 1130 * @param int $line The line on which the warning occurred. 1131 * @param int $column The column at which the warning occurred. 1132 * @param string $code A violation code unique to the sniff message. 1133 * @param array $data Replacements for the warning message. 1134 * @param int $severity The severity level for this warning. A value of 0 1135 * will be converted into the default severity level. 1136 * @param boolean $fixable Can the warning be fixed by the sniff? 1137 * 1138 * @return boolean 1139 */ 1140 private function _addWarning($warning, $line, $column, $code, $data, $severity, $fixable) 1141 { 1142 if (isset(self::$_ignoredLines[$line]) === true) { 1143 return false; 1144 } 1145 1146 // Work out which sniff generated the warning. 1147 if (substr($code, 0, 9) === 'Internal.') { 1148 // Any internal message. 1149 $sniffCode = $code; 1150 } else { 1151 $parts = explode('_', str_replace('\\', '_', $this->_activeListener)); 1152 if (isset($parts[3]) === true) { 1153 $sniff = $parts[0].'.'.$parts[2].'.'.$parts[3]; 1154 1155 // Remove "Sniff" from the end. 1156 $sniff = substr($sniff, 0, -5); 1157 } else { 1158 $sniff = 'unknownSniff'; 1159 } 1160 1161 $sniffCode = $sniff; 1162 if ($code !== '') { 1163 $sniffCode .= '.'.$code; 1164 } 1165 }//end if 1166 1167 // If we know this sniff code is being ignored for this file, return early. 1168 if (isset($this->_ignoredCodes[$sniffCode]) === true) { 1169 return false; 1170 } 1171 1172 // Make sure this message type has not been set to "error". 1173 if (isset($this->ruleset[$sniffCode]['type']) === true 1174 && $this->ruleset[$sniffCode]['type'] === 'error' 1175 ) { 1176 // Pass this off to the error handler. 1177 return $this->_addError($warning, $line, $column, $code, $data, $severity, $fixable); 1178 } else if ($this->phpcs->cli->warningSeverity === 0) { 1179 // Don't bother doing any processing as warnings are just going to 1180 // be hidden in the reports anyway. 1181 return false; 1182 } 1183 1184 // Make sure we are interested in this severity level. 1185 if (isset($this->ruleset[$sniffCode]['severity']) === true) { 1186 $severity = $this->ruleset[$sniffCode]['severity']; 1187 } else if ($severity === 0) { 1188 $severity = PHPCS_DEFAULT_WARN_SEV; 1189 } 1190 1191 if ($this->phpcs->cli->warningSeverity > $severity) { 1192 return false; 1193 } 1194 1195 // Make sure we are not ignoring this file. 1196 $patterns = $this->phpcs->getIgnorePatterns($sniffCode); 1197 foreach ($patterns as $pattern => $type) { 1198 // While there is support for a type of each pattern 1199 // (absolute or relative) we don't actually support it here. 1200 $replacements = array( 1201 '\\,' => ',', 1202 '*' => '.*', 1203 ); 1204 1205 // We assume a / directory separator, as do the exclude rules 1206 // most developers write, so we need a special case for any system 1207 // that is different. 1208 if (DIRECTORY_SEPARATOR === '\\') { 1209 $replacements['/'] = '\\\\'; 1210 } 1211 1212 $pattern = '`'.strtr($pattern, $replacements).'`i'; 1213 if (preg_match($pattern, $this->_file) === 1) { 1214 $this->_ignoredCodes[$sniffCode] = true; 1215 return false; 1216 } 1217 }//end foreach 1218 1219 $this->_warningCount++; 1220 if ($fixable === true) { 1221 $this->_fixableCount++; 1222 } 1223 1224 if ($this->_recordErrors === false) { 1225 if (isset($this->_warnings[$line]) === false) { 1226 $this->_warnings[$line] = 0; 1227 } 1228 1229 $this->_warnings[$line]++; 1230 return true; 1231 } 1232 1233 // Work out the warning message. 1234 if (isset($this->ruleset[$sniffCode]['message']) === true) { 1235 $warning = $this->ruleset[$sniffCode]['message']; 1236 } 1237 1238 if (empty($data) === true) { 1239 $message = $warning; 1240 } else { 1241 $message = vsprintf($warning, $data); 1242 } 1243 1244 if (isset($this->_warnings[$line]) === false) { 1245 $this->_warnings[$line] = array(); 1246 } 1247 1248 if (isset($this->_warnings[$line][$column]) === false) { 1249 $this->_warnings[$line][$column] = array(); 1250 } 1251 1252 $this->_warnings[$line][$column][] = array( 1253 'message' => $message, 1254 'source' => $sniffCode, 1255 'severity' => $severity, 1256 'fixable' => $fixable, 1257 ); 1258 1259 if (PHP_CODESNIFFER_VERBOSITY > 1 1260 && $this->fixer->enabled === true 1261 && $fixable === true 1262 ) { 1263 @ob_end_clean(); 1264 echo "\tW: $message ($sniffCode)".PHP_EOL; 1265 ob_start(); 1266 } 1267 1268 return true; 1269 1270 }//end _addWarning() 1271 1272 1273 /** 1274 * Adds an warning to the warning stack. 1275 * 1276 * @param int $stackPtr The stack position where the metric was recorded. 1277 * @param string $metric The name of the metric being recorded. 1278 * @param string $value The value of the metric being recorded. 1279 * 1280 * @return boolean 1281 */ 1282 public function recordMetric($stackPtr, $metric, $value) 1283 { 1284 if (isset($this->_metrics[$metric]) === false) { 1285 $this->_metrics[$metric] = array( 1286 'values' => array( 1287 $value => array($stackPtr), 1288 ), 1289 ); 1290 } else { 1291 if (isset($this->_metrics[$metric]['values'][$value]) === false) { 1292 $this->_metrics[$metric]['values'][$value] = array($stackPtr); 1293 } else { 1294 $this->_metrics[$metric]['values'][$value][] = $stackPtr; 1295 } 1296 } 1297 1298 return true; 1299 1300 }//end recordMetric() 1301 1302 1303 /** 1304 * Returns the number of errors raised. 1305 * 1306 * @return int 1307 */ 1308 public function getErrorCount() 1309 { 1310 return $this->_errorCount; 1311 1312 }//end getErrorCount() 1313 1314 1315 /** 1316 * Returns the number of warnings raised. 1317 * 1318 * @return int 1319 */ 1320 public function getWarningCount() 1321 { 1322 return $this->_warningCount; 1323 1324 }//end getWarningCount() 1325 1326 1327 /** 1328 * Returns the number of successes recorded. 1329 * 1330 * @return int 1331 */ 1332 public function getSuccessCount() 1333 { 1334 return $this->_successCount; 1335 1336 }//end getSuccessCount() 1337 1338 1339 /** 1340 * Returns the number of fixable errors/warnings raised. 1341 * 1342 * @return int 1343 */ 1344 public function getFixableCount() 1345 { 1346 return $this->_fixableCount; 1347 1348 }//end getFixableCount() 1349 1350 1351 /** 1352 * Returns the list of ignored lines. 1353 * 1354 * @return array 1355 */ 1356 public function getIgnoredLines() 1357 { 1358 return self::$_ignoredLines; 1359 1360 }//end getIgnoredLines() 1361 1362 1363 /** 1364 * Returns the errors raised from processing this file. 1365 * 1366 * @return array 1367 */ 1368 public function getErrors() 1369 { 1370 return $this->_errors; 1371 1372 }//end getErrors() 1373 1374 1375 /** 1376 * Returns the warnings raised from processing this file. 1377 * 1378 * @return array 1379 */ 1380 public function getWarnings() 1381 { 1382 return $this->_warnings; 1383 1384 }//end getWarnings() 1385 1386 1387 /** 1388 * Returns the metrics found while processing this file. 1389 * 1390 * @return array 1391 */ 1392 public function getMetrics() 1393 { 1394 return $this->_metrics; 1395 1396 }//end getMetrics() 1397 1398 1399 /** 1400 * Returns the absolute filename of this file. 1401 * 1402 * @return string 1403 */ 1404 public function getFilename() 1405 { 1406 return $this->_file; 1407 1408 }//end getFilename() 1409 1410 1411 /** 1412 * Creates an array of tokens when given some PHP code. 1413 * 1414 * Starts by using token_get_all() but does a lot of extra processing 1415 * to insert information about the context of the token. 1416 * 1417 * @param string $string The string to tokenize. 1418 * @param object $tokenizer A tokenizer class to use to tokenize the string. 1419 * @param string $eolChar The EOL character to use for splitting strings. 1420 * @param int $tabWidth The number of spaces each tab respresents. 1421 * @param string $encoding The charset of the sniffed file. 1422 * 1423 * @throws PHP_CodeSniffer_Exception If the file cannot be processed. 1424 * @return array 1425 */ 1426 public static function tokenizeString($string, $tokenizer, $eolChar='\n', $tabWidth=null, $encoding=null) 1427 { 1428 // Minified files often have a very large number of characters per line 1429 // and cause issues when tokenizing. 1430 if (property_exists($tokenizer, 'skipMinified') === true 1431 && $tokenizer->skipMinified === true 1432 ) { 1433 $numChars = strlen($string); 1434 $numLines = (substr_count($string, $eolChar) + 1); 1435 $average = ($numChars / $numLines); 1436 if ($average > 100) { 1437 throw new PHP_CodeSniffer_Exception('File appears to be minified and cannot be processed'); 1438 } 1439 } 1440 1441 $tokens = $tokenizer->tokenizeString($string, $eolChar); 1442 1443 if ($tabWidth === null) { 1444 $tabWidth = PHP_CODESNIFFER_TAB_WIDTH; 1445 } 1446 1447 if ($encoding === null) { 1448 $encoding = PHP_CODESNIFFER_ENCODING; 1449 } 1450 1451 self::_createPositionMap($tokens, $tokenizer, $eolChar, $encoding, $tabWidth); 1452 self::_createTokenMap($tokens, $tokenizer, $eolChar); 1453 self::_createParenthesisNestingMap($tokens, $tokenizer, $eolChar); 1454 self::_createScopeMap($tokens, $tokenizer, $eolChar); 1455 1456 self::_createLevelMap($tokens, $tokenizer, $eolChar); 1457 1458 // Allow the tokenizer to do additional processing if required. 1459 $tokenizer->processAdditional($tokens, $eolChar); 1460 1461 return $tokens; 1462 1463 }//end tokenizeString() 1464 1465 1466 /** 1467 * Sets token position information. 1468 * 1469 * Can also convert tabs into spaces. Each tab can represent between 1470 * 1 and $width spaces, so this cannot be a straight string replace. 1471 * 1472 * @param array $tokens The array of tokens to process. 1473 * @param object $tokenizer The tokenizer being used to process this file. 1474 * @param string $eolChar The EOL character to use for splitting strings. 1475 * @param string $encoding The charset of the sniffed file. 1476 * @param int $tabWidth The number of spaces that each tab represents. 1477 * Set to 0 to disable tab replacement. 1478 * 1479 * @return void 1480 */ 1481 private static function _createPositionMap(&$tokens, $tokenizer, $eolChar, $encoding, $tabWidth) 1482 { 1483 $currColumn = 1; 1484 $lineNumber = 1; 1485 $eolLen = (strlen($eolChar) * -1); 1486 $tokenizerType = get_class($tokenizer); 1487 $ignoring = false; 1488 $inTests = defined('PHP_CODESNIFFER_IN_TESTS'); 1489 1490 $checkEncoding = false; 1491 if ($encoding !== 'iso-8859-1' && function_exists('iconv_strlen') === true) { 1492 $checkEncoding = true; 1493 } 1494 1495 $tokensWithTabs = array( 1496 T_WHITESPACE => true, 1497 T_COMMENT => true, 1498 T_DOC_COMMENT => true, 1499 T_DOC_COMMENT_WHITESPACE => true, 1500 T_DOC_COMMENT_STRING => true, 1501 T_CONSTANT_ENCAPSED_STRING => true, 1502 T_DOUBLE_QUOTED_STRING => true, 1503 T_HEREDOC => true, 1504 T_NOWDOC => true, 1505 T_INLINE_HTML => true, 1506 ); 1507 1508 $numTokens = count($tokens); 1509 for ($i = 0; $i < $numTokens; $i++) { 1510 $tokens[$i]['line'] = $lineNumber; 1511 $tokens[$i]['column'] = $currColumn; 1512 1513 if ($tokenizerType === 'PHP_CodeSniffer_Tokenizers_PHP' 1514 && isset(PHP_CodeSniffer_Tokens::$knownLengths[$tokens[$i]['code']]) === true 1515 ) { 1516 // There are no tabs in the tokens we know the length of. 1517 $length = PHP_CodeSniffer_Tokens::$knownLengths[$tokens[$i]['code']]; 1518 $currColumn += $length; 1519 } else if ($tabWidth === 0 1520 || isset($tokensWithTabs[$tokens[$i]['code']]) === false 1521 || strpos($tokens[$i]['content'], "\t") === false 1522 ) { 1523 // There are no tabs in this content, or we aren't replacing them. 1524 if ($checkEncoding === true) { 1525 // Not using the default encoding, so take a bit more care. 1526 $length = @iconv_strlen($tokens[$i]['content'], $encoding); 1527 if ($length === false) { 1528 // String contained invalid characters, so revert to default. 1529 $length = strlen($tokens[$i]['content']); 1530 } 1531 } else { 1532 $length = strlen($tokens[$i]['content']); 1533 } 1534 1535 $currColumn += $length; 1536 } else { 1537 if (str_replace("\t", '', $tokens[$i]['content']) === '') { 1538 // String only contains tabs, so we can shortcut the process. 1539 $numTabs = strlen($tokens[$i]['content']); 1540 1541 $newContent = ''; 1542 $firstTabSize = ($tabWidth - (($currColumn - 1) % $tabWidth)); 1543 $length = ($firstTabSize + ($tabWidth * ($numTabs - 1))); 1544 $currColumn += $length; 1545 $newContent = str_repeat(' ', $length); 1546 } else { 1547 // We need to determine the length of each tab. 1548 $tabs = explode("\t", $tokens[$i]['content']); 1549 1550 $numTabs = (count($tabs) - 1); 1551 $tabNum = 0; 1552 $newContent = ''; 1553 $length = 0; 1554 1555 foreach ($tabs as $content) { 1556 if ($content !== '') { 1557 $newContent .= $content; 1558 if ($checkEncoding === true) { 1559 // Not using the default encoding, so take a bit more care. 1560 $contentLength = @iconv_strlen($content, $encoding); 1561 if ($contentLength === false) { 1562 // String contained invalid characters, so revert to default. 1563 $contentLength = strlen($content); 1564 } 1565 } else { 1566 $contentLength = strlen($content); 1567 } 1568 1569 $currColumn += $contentLength; 1570 $length += $contentLength; 1571 } 1572 1573 // The last piece of content does not have a tab after it. 1574 if ($tabNum === $numTabs) { 1575 break; 1576 } 1577 1578 // Process the tab that comes after the content. 1579 $lastCurrColumn = $currColumn; 1580 $tabNum++; 1581 1582 // Move the pointer to the next tab stop. 1583 if (($currColumn % $tabWidth) === 0) { 1584 // This is the first tab, and we are already at a 1585 // tab stop, so this tab counts as a single space. 1586 $currColumn++; 1587 } else { 1588 $currColumn++; 1589 while (($currColumn % $tabWidth) !== 0) { 1590 $currColumn++; 1591 } 1592 1593 $currColumn++; 1594 } 1595 1596 $length += ($currColumn - $lastCurrColumn); 1597 $newContent .= str_repeat(' ', ($currColumn - $lastCurrColumn)); 1598 }//end foreach 1599 }//end if 1600 1601 $tokens[$i]['orig_content'] = $tokens[$i]['content']; 1602 $tokens[$i]['content'] = $newContent; 1603 }//end if 1604 1605 $tokens[$i]['length'] = $length; 1606 1607 if (isset(PHP_CodeSniffer_Tokens::$knownLengths[$tokens[$i]['code']]) === false 1608 && strpos($tokens[$i]['content'], $eolChar) !== false 1609 ) { 1610 $lineNumber++; 1611 $currColumn = 1; 1612 1613 // Newline chars are not counted in the token length. 1614 $tokens[$i]['length'] += $eolLen; 1615 } 1616 1617 if ($tokens[$i]['code'] === T_COMMENT 1618 || $tokens[$i]['code'] === T_DOC_COMMENT_TAG 1619 || ($inTests === true && $tokens[$i]['code'] === T_INLINE_HTML) 1620 ) { 1621 if (strpos($tokens[$i]['content'], '@codingStandards') !== false) { 1622 if ($ignoring === false 1623 && strpos($tokens[$i]['content'], '@codingStandardsIgnoreStart') !== false 1624 ) { 1625 $ignoring = true; 1626 } else if ($ignoring === true 1627 && strpos($tokens[$i]['content'], '@codingStandardsIgnoreEnd') !== false 1628 ) { 1629 $ignoring = false; 1630 // Ignore this comment too. 1631 self::$_ignoredLines[$tokens[$i]['line']] = true; 1632 } else if ($ignoring === false 1633 && strpos($tokens[$i]['content'], '@codingStandardsIgnoreLine') !== false 1634 ) { 1635 self::$_ignoredLines[($tokens[$i]['line'] + 1)] = true; 1636 // Ignore this comment too. 1637 self::$_ignoredLines[$tokens[$i]['line']] = true; 1638 } 1639 } 1640 }//end if 1641 1642 if ($ignoring === true) { 1643 self::$_ignoredLines[$tokens[$i]['line']] = true; 1644 } 1645 }//end for 1646 1647 }//end _createPositionMap() 1648 1649 1650 /** 1651 * Creates a map of brackets positions. 1652 * 1653 * @param array $tokens The array of tokens to process. 1654 * @param object $tokenizer The tokenizer being used to process this file. 1655 * @param string $eolChar The EOL character to use for splitting strings. 1656 * 1657 * @return void 1658 */ 1659 private static function _createTokenMap(&$tokens, $tokenizer, $eolChar) 1660 { 1661 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1662 echo "\t*** START TOKEN MAP ***".PHP_EOL; 1663 } 1664 1665 $squareOpeners = array(); 1666 $curlyOpeners = array(); 1667 $numTokens = count($tokens); 1668 1669 $openers = array(); 1670 $openOwner = null; 1671 1672 for ($i = 0; $i < $numTokens; $i++) { 1673 /* 1674 Parenthesis mapping. 1675 */ 1676 1677 if (isset(PHP_CodeSniffer_Tokens::$parenthesisOpeners[$tokens[$i]['code']]) === true) { 1678 $tokens[$i]['parenthesis_opener'] = null; 1679 $tokens[$i]['parenthesis_closer'] = null; 1680 $tokens[$i]['parenthesis_owner'] = $i; 1681 $openOwner = $i; 1682 } else if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) { 1683 $openers[] = $i; 1684 $tokens[$i]['parenthesis_opener'] = $i; 1685 if ($openOwner !== null) { 1686 $tokens[$openOwner]['parenthesis_opener'] = $i; 1687 $tokens[$i]['parenthesis_owner'] = $openOwner; 1688 $openOwner = null; 1689 } 1690 } else if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) { 1691 // Did we set an owner for this set of parenthesis? 1692 $numOpeners = count($openers); 1693 if ($numOpeners !== 0) { 1694 $opener = array_pop($openers); 1695 if (isset($tokens[$opener]['parenthesis_owner']) === true) { 1696 $owner = $tokens[$opener]['parenthesis_owner']; 1697 1698 $tokens[$owner]['parenthesis_closer'] = $i; 1699 $tokens[$i]['parenthesis_owner'] = $owner; 1700 } 1701 1702 $tokens[$i]['parenthesis_opener'] = $opener; 1703 $tokens[$i]['parenthesis_closer'] = $i; 1704 $tokens[$opener]['parenthesis_closer'] = $i; 1705 } 1706 }//end if 1707 1708 /* 1709 Bracket mapping. 1710 */ 1711 1712 switch ($tokens[$i]['code']) { 1713 case T_OPEN_SQUARE_BRACKET: 1714 $squareOpeners[] = $i; 1715 1716 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1717 echo str_repeat("\t", count($squareOpeners)); 1718 echo str_repeat("\t", count($curlyOpeners)); 1719 echo "=> Found square bracket opener at $i".PHP_EOL; 1720 } 1721 break; 1722 case T_OPEN_CURLY_BRACKET: 1723 if (isset($tokens[$i]['scope_closer']) === false) { 1724 $curlyOpeners[] = $i; 1725 1726 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1727 echo str_repeat("\t", count($squareOpeners)); 1728 echo str_repeat("\t", count($curlyOpeners)); 1729 echo "=> Found curly bracket opener at $i".PHP_EOL; 1730 } 1731 } 1732 break; 1733 case T_CLOSE_SQUARE_BRACKET: 1734 if (empty($squareOpeners) === false) { 1735 $opener = array_pop($squareOpeners); 1736 $tokens[$i]['bracket_opener'] = $opener; 1737 $tokens[$i]['bracket_closer'] = $i; 1738 $tokens[$opener]['bracket_opener'] = $opener; 1739 $tokens[$opener]['bracket_closer'] = $i; 1740 1741 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1742 echo str_repeat("\t", count($squareOpeners)); 1743 echo str_repeat("\t", count($curlyOpeners)); 1744 echo "\t=> Found square bracket closer at $i for $opener".PHP_EOL; 1745 } 1746 } 1747 break; 1748 case T_CLOSE_CURLY_BRACKET: 1749 if (empty($curlyOpeners) === false 1750 && isset($tokens[$i]['scope_opener']) === false 1751 ) { 1752 $opener = array_pop($curlyOpeners); 1753 $tokens[$i]['bracket_opener'] = $opener; 1754 $tokens[$i]['bracket_closer'] = $i; 1755 $tokens[$opener]['bracket_opener'] = $opener; 1756 $tokens[$opener]['bracket_closer'] = $i; 1757 1758 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1759 echo str_repeat("\t", count($squareOpeners)); 1760 echo str_repeat("\t", count($curlyOpeners)); 1761 echo "\t=> Found curly bracket closer at $i for $opener".PHP_EOL; 1762 } 1763 } 1764 break; 1765 default: 1766 continue; 1767 }//end switch 1768 }//end for 1769 1770 // Cleanup for any openers that we didn't find closers for. 1771 // This typically means there was a syntax error breaking things. 1772 foreach ($openers as $opener) { 1773 unset($tokens[$opener]['parenthesis_opener']); 1774 unset($tokens[$opener]['parenthesis_owner']); 1775 } 1776 1777 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1778 echo "\t*** END TOKEN MAP ***".PHP_EOL; 1779 } 1780 1781 }//end _createTokenMap() 1782 1783 1784 /** 1785 * Creates a map for the parenthesis tokens that surround other tokens. 1786 * 1787 * @param array $tokens The array of tokens to process. 1788 * @param object $tokenizer The tokenizer being used to process this file. 1789 * @param string $eolChar The EOL character to use for splitting strings. 1790 * 1791 * @return void 1792 */ 1793 private static function _createParenthesisNestingMap( 1794 &$tokens, 1795 $tokenizer, 1796 $eolChar 1797 ) { 1798 $numTokens = count($tokens); 1799 $map = array(); 1800 for ($i = 0; $i < $numTokens; $i++) { 1801 if (isset($tokens[$i]['parenthesis_opener']) === true 1802 && $i === $tokens[$i]['parenthesis_opener'] 1803 ) { 1804 if (empty($map) === false) { 1805 $tokens[$i]['nested_parenthesis'] = $map; 1806 } 1807 1808 if (isset($tokens[$i]['parenthesis_closer']) === true) { 1809 $map[$tokens[$i]['parenthesis_opener']] 1810 = $tokens[$i]['parenthesis_closer']; 1811 } 1812 } else if (isset($tokens[$i]['parenthesis_closer']) === true 1813 && $i === $tokens[$i]['parenthesis_closer'] 1814 ) { 1815 array_pop($map); 1816 if (empty($map) === false) { 1817 $tokens[$i]['nested_parenthesis'] = $map; 1818 } 1819 } else { 1820 if (empty($map) === false) { 1821 $tokens[$i]['nested_parenthesis'] = $map; 1822 } 1823 }//end if 1824 }//end for 1825 1826 }//end _createParenthesisNestingMap() 1827 1828 1829 /** 1830 * Creates a scope map of tokens that open scopes. 1831 * 1832 * @param array $tokens The array of tokens to process. 1833 * @param object $tokenizer The tokenizer being used to process this file. 1834 * @param string $eolChar The EOL character to use for splitting strings. 1835 * 1836 * @return void 1837 * @see _recurseScopeMap() 1838 */ 1839 private static function _createScopeMap(&$tokens, $tokenizer, $eolChar) 1840 { 1841 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1842 echo "\t*** START SCOPE MAP ***".PHP_EOL; 1843 } 1844 1845 $numTokens = count($tokens); 1846 for ($i = 0; $i < $numTokens; $i++) { 1847 // Check to see if the current token starts a new scope. 1848 if (isset($tokenizer->scopeOpeners[$tokens[$i]['code']]) === true) { 1849 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1850 $type = $tokens[$i]['type']; 1851 $content = PHP_CodeSniffer::prepareForOutput($tokens[$i]['content']); 1852 echo "\tStart scope map at $i:$type => $content".PHP_EOL; 1853 } 1854 1855 if (isset($tokens[$i]['scope_condition']) === true) { 1856 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1857 echo "\t* already processed, skipping *".PHP_EOL; 1858 } 1859 1860 continue; 1861 } 1862 1863 $i = self::_recurseScopeMap( 1864 $tokens, 1865 $numTokens, 1866 $tokenizer, 1867 $eolChar, 1868 $i 1869 ); 1870 }//end if 1871 }//end for 1872 1873 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1874 echo "\t*** END SCOPE MAP ***".PHP_EOL; 1875 } 1876 1877 }//end _createScopeMap() 1878 1879 1880 /** 1881 * Recurses though the scope openers to build a scope map. 1882 * 1883 * @param array $tokens The array of tokens to process. 1884 * @param int $numTokens The size of the tokens array. 1885 * @param object $tokenizer The tokenizer being used to process this file. 1886 * @param string $eolChar The EOL character to use for splitting strings. 1887 * @param int $stackPtr The position in the stack of the token that 1888 * opened the scope (eg. an IF token or FOR token). 1889 * @param int $depth How many scope levels down we are. 1890 * @param int $ignore How many curly braces we are ignoring. 1891 * 1892 * @return int The position in the stack that closed the scope. 1893 */ 1894 private static function _recurseScopeMap( 1895 &$tokens, 1896 $numTokens, 1897 $tokenizer, 1898 $eolChar, 1899 $stackPtr, 1900 $depth=1, 1901 &$ignore=0 1902 ) { 1903 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1904 echo str_repeat("\t", $depth); 1905 echo "=> Begin scope map recursion at token $stackPtr with depth $depth".PHP_EOL; 1906 } 1907 1908 $opener = null; 1909 $currType = $tokens[$stackPtr]['code']; 1910 $startLine = $tokens[$stackPtr]['line']; 1911 1912 // We will need this to restore the value if we end up 1913 // returning a token ID that causes our calling function to go back 1914 // over already ignored braces. 1915 $originalIgnore = $ignore; 1916 1917 // If the start token for this scope opener is the same as 1918 // the scope token, we have already found our opener. 1919 if (isset($tokenizer->scopeOpeners[$currType]['start'][$currType]) === true) { 1920 $opener = $stackPtr; 1921 } 1922 1923 for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { 1924 $tokenType = $tokens[$i]['code']; 1925 1926 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1927 $type = $tokens[$i]['type']; 1928 $line = $tokens[$i]['line']; 1929 $content = PHP_CodeSniffer::prepareForOutput($tokens[$i]['content']); 1930 1931 echo str_repeat("\t", $depth); 1932 echo "Process token $i on line $line ["; 1933 if ($opener !== null) { 1934 echo "opener:$opener;"; 1935 } 1936 1937 if ($ignore > 0) { 1938 echo "ignore=$ignore;"; 1939 } 1940 1941 echo "]: $type => $content".PHP_EOL; 1942 }//end if 1943 1944 // Very special case for IF statements in PHP that can be defined without 1945 // scope tokens. E.g., if (1) 1; 1 ? (1 ? 1 : 1) : 1; 1946 // If an IF statement below this one has an opener but no 1947 // keyword, the opener will be incorrectly assigned to this IF statement. 1948 // The same case also applies to USE statements, which don't have to have 1949 // openers, so a following USE statement can cause an incorrect brace match. 1950 if (($currType === T_IF || $currType === T_ELSE || $currType === T_USE) 1951 && $opener === null 1952 && $tokens[$i]['code'] === T_SEMICOLON 1953 ) { 1954 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1955 $type = $tokens[$stackPtr]['type']; 1956 echo str_repeat("\t", $depth); 1957 echo "=> Found semicolon before scope opener for $stackPtr:$type, bailing".PHP_EOL; 1958 } 1959 1960 return $i; 1961 } 1962 1963 if ($opener === null 1964 && $ignore === 0 1965 && $tokenType === T_CLOSE_CURLY_BRACKET 1966 && isset($tokenizer->scopeOpeners[$currType]['end'][$tokenType]) === true 1967 ) { 1968 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1969 $type = $tokens[$stackPtr]['type']; 1970 echo str_repeat("\t", $depth); 1971 echo "=> Found curly brace closer before scope opener for $stackPtr:$type, bailing".PHP_EOL; 1972 } 1973 1974 return ($i - 1); 1975 } 1976 1977 if ($opener !== null 1978 && (isset($tokens[$i]['scope_opener']) === false 1979 || $tokenizer->scopeOpeners[$tokens[$stackPtr]['code']]['shared'] === true) 1980 && isset($tokenizer->scopeOpeners[$currType]['end'][$tokenType]) === true 1981 ) { 1982 if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) { 1983 // The last opening bracket must have been for a string 1984 // offset or alike, so let's ignore it. 1985 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1986 echo str_repeat("\t", $depth); 1987 echo '* finished ignoring curly brace *'.PHP_EOL; 1988 } 1989 1990 $ignore--; 1991 continue; 1992 } else if ($tokens[$opener]['code'] === T_OPEN_CURLY_BRACKET 1993 && $tokenType !== T_CLOSE_CURLY_BRACKET 1994 ) { 1995 // The opener is a curly bracket so the closer must be a curly bracket as well. 1996 // We ignore this closer to handle cases such as T_ELSE or T_ELSEIF being considered 1997 // a closer of T_IF when it should not. 1998 if (PHP_CODESNIFFER_VERBOSITY > 1) { 1999 $type = $tokens[$stackPtr]['type']; 2000 echo str_repeat("\t", $depth); 2001 echo "=> Ignoring non-curly scope closer for $stackPtr:$type".PHP_EOL; 2002 } 2003 } else { 2004 $scopeCloser = $i; 2005 $todo = array( 2006 $stackPtr, 2007 $opener, 2008 ); 2009 2010 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2011 $type = $tokens[$stackPtr]['type']; 2012 $closerType = $tokens[$scopeCloser]['type']; 2013 echo str_repeat("\t", $depth); 2014 echo "=> Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type".PHP_EOL; 2015 } 2016 2017 $validCloser = true; 2018 if (($tokens[$stackPtr]['code'] === T_IF || $tokens[$stackPtr]['code'] === T_ELSEIF) 2019 && ($tokenType === T_ELSE || $tokenType === T_ELSEIF) 2020 ) { 2021 // To be a closer, this token must have an opener. 2022 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2023 echo str_repeat("\t", $depth); 2024 echo "* closer needs to be tested *".PHP_EOL; 2025 } 2026 2027 $i = self::_recurseScopeMap( 2028 $tokens, 2029 $numTokens, 2030 $tokenizer, 2031 $eolChar, 2032 $i, 2033 ($depth + 1), 2034 $ignore 2035 ); 2036 2037 if (isset($tokens[$scopeCloser]['scope_opener']) === false) { 2038 $validCloser = false; 2039 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2040 echo str_repeat("\t", $depth); 2041 echo "* closer is not valid (no opener found) *".PHP_EOL; 2042 } 2043 } else if ($tokens[$tokens[$scopeCloser]['scope_opener']]['code'] !== $tokens[$opener]['code']) { 2044 $validCloser = false; 2045 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2046 echo str_repeat("\t", $depth); 2047 $type = $tokens[$tokens[$scopeCloser]['scope_opener']]['type']; 2048 $openerType = $tokens[$opener]['type']; 2049 echo "* closer is not valid (mismatched opener type; $type != $openerType) *".PHP_EOL; 2050 } 2051 } else if (PHP_CODESNIFFER_VERBOSITY > 1) { 2052 echo str_repeat("\t", $depth); 2053 echo "* closer was valid *".PHP_EOL; 2054 } 2055 } else { 2056 // The closer was not processed, so we need to 2057 // complete that token as well. 2058 $todo[] = $scopeCloser; 2059 }//end if 2060 2061 if ($validCloser === true) { 2062 foreach ($todo as $token) { 2063 $tokens[$token]['scope_condition'] = $stackPtr; 2064 $tokens[$token]['scope_opener'] = $opener; 2065 $tokens[$token]['scope_closer'] = $scopeCloser; 2066 } 2067 2068 if ($tokenizer->scopeOpeners[$tokens[$stackPtr]['code']]['shared'] === true) { 2069 // As we are going back to where we started originally, restore 2070 // the ignore value back to its original value. 2071 $ignore = $originalIgnore; 2072 return $opener; 2073 } else if ($scopeCloser === $i 2074 && isset($tokenizer->scopeOpeners[$tokenType]) === true 2075 ) { 2076 // Unset scope_condition here or else the token will appear to have 2077 // already been processed, and it will be skipped. Normally we want that, 2078 // but in this case, the token is both a closer and an opener, so 2079 // it needs to act like an opener. This is also why we return the 2080 // token before this one; so the closer has a chance to be processed 2081 // a second time, but as an opener. 2082 unset($tokens[$scopeCloser]['scope_condition']); 2083 return ($i - 1); 2084 } else { 2085 return $i; 2086 } 2087 } else { 2088 continue; 2089 }//end if 2090 }//end if 2091 }//end if 2092 2093 // Is this an opening condition ? 2094 if (isset($tokenizer->scopeOpeners[$tokenType]) === true) { 2095 if ($opener === null) { 2096 if ($tokenType === T_USE) { 2097 // PHP use keywords are special because they can be 2098 // used as blocks but also inline in function definitions. 2099 // So if we find them nested inside another opener, just skip them. 2100 continue; 2101 } 2102 2103 if ($tokenType === T_FUNCTION 2104 && $tokens[$stackPtr]['code'] !== T_FUNCTION 2105 ) { 2106 // Probably a closure, so process it manually. 2107 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2108 $type = $tokens[$stackPtr]['type']; 2109 echo str_repeat("\t", $depth); 2110 echo "=> Found function before scope opener for $stackPtr:$type, processing manually".PHP_EOL; 2111 } 2112 2113 if (isset($tokens[$i]['scope_closer']) === true) { 2114 // We've already processed this closure. 2115 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2116 echo str_repeat("\t", $depth); 2117 echo '* already processed, skipping *'.PHP_EOL; 2118 } 2119 2120 $i = $tokens[$i]['scope_closer']; 2121 continue; 2122 } 2123 2124 $i = self::_recurseScopeMap( 2125 $tokens, 2126 $numTokens, 2127 $tokenizer, 2128 $eolChar, 2129 $i, 2130 ($depth + 1), 2131 $ignore 2132 ); 2133 2134 continue; 2135 }//end if 2136 2137 // Found another opening condition but still haven't 2138 // found our opener, so we are never going to find one. 2139 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2140 $type = $tokens[$stackPtr]['type']; 2141 echo str_repeat("\t", $depth); 2142 echo "=> Found new opening condition before scope opener for $stackPtr:$type, "; 2143 } 2144 2145 if (($tokens[$stackPtr]['code'] === T_IF 2146 || $tokens[$stackPtr]['code'] === T_ELSEIF 2147 || $tokens[$stackPtr]['code'] === T_ELSE) 2148 && ($tokens[$i]['code'] === T_ELSE 2149 || $tokens[$i]['code'] === T_ELSEIF) 2150 ) { 2151 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2152 echo "continuing".PHP_EOL; 2153 } 2154 2155 return ($i - 1); 2156 } else { 2157 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2158 echo "backtracking".PHP_EOL; 2159 } 2160 2161 return $stackPtr; 2162 } 2163 }//end if 2164 2165 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2166 echo str_repeat("\t", $depth); 2167 echo '* token is an opening condition *'.PHP_EOL; 2168 } 2169 2170 $isShared = ($tokenizer->scopeOpeners[$tokenType]['shared'] === true); 2171 2172 if (isset($tokens[$i]['scope_condition']) === true) { 2173 // We've been here before. 2174 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2175 echo str_repeat("\t", $depth); 2176 echo '* already processed, skipping *'.PHP_EOL; 2177 } 2178 2179 if ($isShared === false 2180 && isset($tokens[$i]['scope_closer']) === true 2181 ) { 2182 $i = $tokens[$i]['scope_closer']; 2183 } 2184 2185 continue; 2186 } else if ($currType === $tokenType 2187 && $isShared === false 2188 && $opener === null 2189 ) { 2190 // We haven't yet found our opener, but we have found another 2191 // scope opener which is the same type as us, and we don't 2192 // share openers, so we will never find one. 2193 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2194 echo str_repeat("\t", $depth); 2195 echo '* it was another token\'s opener, bailing *'.PHP_EOL; 2196 } 2197 2198 return $stackPtr; 2199 } else { 2200 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2201 echo str_repeat("\t", $depth); 2202 echo '* searching for opener *'.PHP_EOL; 2203 } 2204 2205 if (isset($tokenizer->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) { 2206 $oldIgnore = $ignore; 2207 $ignore = 0; 2208 } 2209 2210 // PHP has a max nesting level for functions. Stop before we hit that limit 2211 // because too many loops means we've run into trouble anyway. 2212 if ($depth > 50) { 2213 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2214 echo str_repeat("\t", $depth); 2215 echo '* reached maximum nesting level; aborting *'.PHP_EOL; 2216 } 2217 2218 throw new PHP_CodeSniffer_Exception('Maximum nesting level reached; file could not be processed'); 2219 } 2220 2221 $oldDepth = $depth; 2222 if ($isShared === true 2223 && isset($tokenizer->scopeOpeners[$tokenType]['with'][$currType]) === true 2224 ) { 2225 // Don't allow the depth to increment because this is 2226 // possibly not a true nesting if we are sharing our closer. 2227 // This can happen, for example, when a SWITCH has a large 2228 // number of CASE statements with the same shared BREAK. 2229 $depth--; 2230 } 2231 2232 $i = self::_recurseScopeMap( 2233 $tokens, 2234 $numTokens, 2235 $tokenizer, 2236 $eolChar, 2237 $i, 2238 ($depth + 1), 2239 $ignore 2240 ); 2241 2242 $depth = $oldDepth; 2243 2244 if (isset($tokenizer->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) { 2245 $ignore = $oldIgnore; 2246 } 2247 }//end if 2248 }//end if 2249 2250 if (isset($tokenizer->scopeOpeners[$currType]['start'][$tokenType]) === true 2251 && $opener === null 2252 ) { 2253 if ($tokenType === T_OPEN_CURLY_BRACKET) { 2254 if (isset($tokens[$stackPtr]['parenthesis_closer']) === true 2255 && $i < $tokens[$stackPtr]['parenthesis_closer'] 2256 ) { 2257 // We found a curly brace inside the condition of the 2258 // current scope opener, so it must be a string offset. 2259 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2260 echo str_repeat("\t", $depth); 2261 echo '* ignoring curly brace inside condition *'.PHP_EOL; 2262 } 2263 2264 $ignore++; 2265 } else { 2266 // Make sure this is actually an opener and not a 2267 // string offset (e.g., $var{0}). 2268 for ($x = ($i - 1); $x > 0; $x--) { 2269 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$x]['code']]) === true) { 2270 continue; 2271 } else { 2272 // If the first non-whitespace/comment token looks like this 2273 // brace is a string offset, or this brace is mid-way through 2274 // a new statement, it isn't a scope opener. 2275 $disallowed = PHP_CodeSniffer_Tokens::$assignmentTokens; 2276 $disallowed += array( 2277 T_VARIABLE => true, 2278 T_OBJECT_OPERATOR => true, 2279 T_COMMA => true, 2280 T_OPEN_PARENTHESIS => true, 2281 ); 2282 2283 if (isset($disallowed[$tokens[$x]['code']]) === true) { 2284 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2285 echo str_repeat("\t", $depth); 2286 echo '* ignoring curly brace after condition *'.PHP_EOL; 2287 } 2288 2289 $ignore++; 2290 }//end if 2291 2292 break; 2293 }//end if 2294 }//end for 2295 }//end if 2296 }//end if 2297 2298 if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) { 2299 // We found the opening scope token for $currType. 2300 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2301 $type = $tokens[$stackPtr]['type']; 2302 echo str_repeat("\t", $depth); 2303 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL; 2304 } 2305 2306 $opener = $i; 2307 } 2308 } else if ($tokenType === T_OPEN_PARENTHESIS) { 2309 if (isset($tokens[$i]['parenthesis_owner']) === true) { 2310 $owner = $tokens[$i]['parenthesis_owner']; 2311 if (isset(PHP_CodeSniffer_Tokens::$scopeOpeners[$tokens[$owner]['code']]) === true 2312 && isset($tokens[$i]['parenthesis_closer']) === true 2313 ) { 2314 // If we get into here, then we opened a parenthesis for 2315 // a scope (eg. an if or else if) so we need to update the 2316 // start of the line so that when we check to see 2317 // if the closing parenthesis is more than 3 lines away from 2318 // the statement, we check from the closing parenthesis. 2319 $startLine = $tokens[$tokens[$i]['parenthesis_closer']]['line']; 2320 } 2321 } 2322 } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) { 2323 // We opened something that we don't have a scope opener for. 2324 // Examples of this are curly brackets for string offsets etc. 2325 // We want to ignore this so that we don't have an invalid scope 2326 // map. 2327 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2328 echo str_repeat("\t", $depth); 2329 echo '* ignoring curly brace *'.PHP_EOL; 2330 } 2331 2332 $ignore++; 2333 } else if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) { 2334 // We found the end token for the opener we were ignoring. 2335 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2336 echo str_repeat("\t", $depth); 2337 echo '* finished ignoring curly brace *'.PHP_EOL; 2338 } 2339 2340 $ignore--; 2341 } else if ($opener === null 2342 && isset($tokenizer->scopeOpeners[$currType]) === true 2343 ) { 2344 // If we still haven't found the opener after 3 lines, 2345 // we're not going to find it, unless we know it requires 2346 // an opener (in which case we better keep looking) or the last 2347 // token was empty (in which case we'll just confirm there is 2348 // more code in this file and not just a big comment). 2349 if ($tokens[$i]['line'] >= ($startLine + 3) 2350 && isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[($i - 1)]['code']]) === false 2351 ) { 2352 if ($tokenizer->scopeOpeners[$currType]['strict'] === true) { 2353 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2354 $type = $tokens[$stackPtr]['type']; 2355 $lines = ($tokens[$i]['line'] - $startLine); 2356 echo str_repeat("\t", $depth); 2357 echo "=> Still looking for $stackPtr:$type scope opener after $lines lines".PHP_EOL; 2358 } 2359 } else { 2360 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2361 $type = $tokens[$stackPtr]['type']; 2362 echo str_repeat("\t", $depth); 2363 echo "=> Couldn't find scope opener for $stackPtr:$type, bailing".PHP_EOL; 2364 } 2365 2366 return $stackPtr; 2367 } 2368 } 2369 } else if ($opener !== null 2370 && $tokenType !== T_BREAK 2371 && isset($tokenizer->endScopeTokens[$tokenType]) === true 2372 ) { 2373 if (isset($tokens[$i]['scope_condition']) === false) { 2374 if ($ignore > 0) { 2375 // We found the end token for the opener we were ignoring. 2376 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2377 echo str_repeat("\t", $depth); 2378 echo '* finished ignoring curly brace *'.PHP_EOL; 2379 } 2380 2381 $ignore--; 2382 } else { 2383 // We found a token that closes the scope but it doesn't 2384 // have a condition, so it belongs to another token and 2385 // our token doesn't have a closer, so pretend this is 2386 // the closer. 2387 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2388 $type = $tokens[$stackPtr]['type']; 2389 echo str_repeat("\t", $depth); 2390 echo "=> Found (unexpected) scope closer for $stackPtr:$type".PHP_EOL; 2391 } 2392 2393 foreach (array($stackPtr, $opener) as $token) { 2394 $tokens[$token]['scope_condition'] = $stackPtr; 2395 $tokens[$token]['scope_opener'] = $opener; 2396 $tokens[$token]['scope_closer'] = $i; 2397 } 2398 2399 return ($i - 1); 2400 }//end if 2401 }//end if 2402 }//end if 2403 }//end for 2404 2405 return $stackPtr; 2406 2407 }//end _recurseScopeMap() 2408 2409 2410 /** 2411 * Constructs the level map. 2412 * 2413 * The level map adds a 'level' index to each token which indicates the 2414 * depth that a token within a set of scope blocks. It also adds a 2415 * 'condition' index which is an array of the scope conditions that opened 2416 * each of the scopes - position 0 being the first scope opener. 2417 * 2418 * @param array $tokens The array of tokens to process. 2419 * @param object $tokenizer The tokenizer being used to process this file. 2420 * @param string $eolChar The EOL character to use for splitting strings. 2421 * 2422 * @return void 2423 */ 2424 private static function _createLevelMap(&$tokens, $tokenizer, $eolChar) 2425 { 2426 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2427 echo "\t*** START LEVEL MAP ***".PHP_EOL; 2428 } 2429 2430 $numTokens = count($tokens); 2431 $level = 0; 2432 $conditions = array(); 2433 $lastOpener = null; 2434 $openers = array(); 2435 2436 for ($i = 0; $i < $numTokens; $i++) { 2437 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2438 $type = $tokens[$i]['type']; 2439 $line = $tokens[$i]['line']; 2440 $len = $tokens[$i]['length']; 2441 $col = $tokens[$i]['column']; 2442 2443 $content = PHP_CodeSniffer::prepareForOutput($tokens[$i]['content']); 2444 2445 echo str_repeat("\t", ($level + 1)); 2446 echo "Process token $i on line $line [col:$col;len:$len;lvl:$level;"; 2447 if (empty($conditions) !== true) { 2448 $condString = 'conds;'; 2449 foreach ($conditions as $condition) { 2450 $condString .= token_name($condition).','; 2451 } 2452 2453 echo rtrim($condString, ',').';'; 2454 } 2455 2456 echo "]: $type => $content".PHP_EOL; 2457 }//end if 2458 2459 $tokens[$i]['level'] = $level; 2460 $tokens[$i]['conditions'] = $conditions; 2461 2462 if (isset($tokens[$i]['scope_condition']) === true) { 2463 // Check to see if this token opened the scope. 2464 if ($tokens[$i]['scope_opener'] === $i) { 2465 $stackPtr = $tokens[$i]['scope_condition']; 2466 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2467 $type = $tokens[$stackPtr]['type']; 2468 echo str_repeat("\t", ($level + 1)); 2469 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL; 2470 } 2471 2472 $stackPtr = $tokens[$i]['scope_condition']; 2473 2474 // If we find a scope opener that has a shared closer, 2475 // then we need to go back over the condition map that we 2476 // just created and fix ourselves as we just added some 2477 // conditions where there was none. This happens for T_CASE 2478 // statements that are using the same break statement. 2479 if ($lastOpener !== null && $tokens[$lastOpener]['scope_closer'] === $tokens[$i]['scope_closer']) { 2480 // This opener shares its closer with the previous opener, 2481 // but we still need to check if the two openers share their 2482 // closer with each other directly (like CASE and DEFAULT) 2483 // or if they are just sharing because one doesn't have a 2484 // closer (like CASE with no BREAK using a SWITCHes closer). 2485 $thisType = $tokens[$tokens[$i]['scope_condition']]['code']; 2486 $opener = $tokens[$lastOpener]['scope_condition']; 2487 2488 $isShared = isset($tokenizer->scopeOpeners[$thisType]['with'][$tokens[$opener]['code']]); 2489 2490 reset($tokenizer->scopeOpeners[$thisType]['end']); 2491 reset($tokenizer->scopeOpeners[$tokens[$opener]['code']]['end']); 2492 $sameEnd = (current($tokenizer->scopeOpeners[$thisType]['end']) === current($tokenizer->scopeOpeners[$tokens[$opener]['code']]['end'])); 2493 2494 if ($isShared === true && $sameEnd === true) { 2495 $badToken = $opener; 2496 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2497 $type = $tokens[$badToken]['type']; 2498 echo str_repeat("\t", ($level + 1)); 2499 echo "* shared closer, cleaning up $badToken:$type *".PHP_EOL; 2500 } 2501 2502 for ($x = $tokens[$i]['scope_condition']; $x <= $i; $x++) { 2503 $oldConditions = $tokens[$x]['conditions']; 2504 $oldLevel = $tokens[$x]['level']; 2505 $tokens[$x]['level']--; 2506 unset($tokens[$x]['conditions'][$badToken]); 2507 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2508 $type = $tokens[$x]['type']; 2509 $oldConds = ''; 2510 foreach ($oldConditions as $condition) { 2511 $oldConds .= token_name($condition).','; 2512 } 2513 2514 $oldConds = rtrim($oldConds, ','); 2515 2516 $newConds = ''; 2517 foreach ($tokens[$x]['conditions'] as $condition) { 2518 $newConds .= token_name($condition).','; 2519 } 2520 2521 $newConds = rtrim($newConds, ','); 2522 2523 $newLevel = $tokens[$x]['level']; 2524 echo str_repeat("\t", ($level + 1)); 2525 echo "* cleaned $x:$type *".PHP_EOL; 2526 echo str_repeat("\t", ($level + 2)); 2527 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL; 2528 echo str_repeat("\t", ($level + 2)); 2529 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL; 2530 }//end if 2531 }//end for 2532 2533 unset($conditions[$badToken]); 2534 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2535 $type = $tokens[$badToken]['type']; 2536 echo str_repeat("\t", ($level + 1)); 2537 echo "* token $badToken:$type removed from conditions array *".PHP_EOL; 2538 } 2539 2540 unset($openers[$lastOpener]); 2541 2542 $level--; 2543 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2544 echo str_repeat("\t", ($level + 2)); 2545 echo '* level decreased *'.PHP_EOL; 2546 } 2547 }//end if 2548 }//end if 2549 2550 $level++; 2551 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2552 echo str_repeat("\t", ($level + 1)); 2553 echo '* level increased *'.PHP_EOL; 2554 } 2555 2556 $conditions[$stackPtr] = $tokens[$stackPtr]['code']; 2557 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2558 $type = $tokens[$stackPtr]['type']; 2559 echo str_repeat("\t", ($level + 1)); 2560 echo "* token $stackPtr:$type added to conditions array *".PHP_EOL; 2561 } 2562 2563 $lastOpener = $tokens[$i]['scope_opener']; 2564 if ($lastOpener !== null) { 2565 $openers[$lastOpener] = $lastOpener; 2566 } 2567 } else if ($lastOpener !== null && $tokens[$lastOpener]['scope_closer'] === $i) { 2568 foreach (array_reverse($openers) as $opener) { 2569 if ($tokens[$opener]['scope_closer'] === $i) { 2570 $oldOpener = array_pop($openers); 2571 if (empty($openers) === false) { 2572 $lastOpener = array_pop($openers); 2573 $openers[$lastOpener] = $lastOpener; 2574 } else { 2575 $lastOpener = null; 2576 } 2577 2578 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2579 $type = $tokens[$oldOpener]['type']; 2580 echo str_repeat("\t", ($level + 1)); 2581 echo "=> Found scope closer for $oldOpener:$type".PHP_EOL; 2582 } 2583 2584 $oldCondition = array_pop($conditions); 2585 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2586 echo str_repeat("\t", ($level + 1)); 2587 echo '* token '.token_name($oldCondition).' removed from conditions array *'.PHP_EOL; 2588 } 2589 2590 // Make sure this closer actually belongs to us. 2591 // Either the condition also has to think this is the 2592 // closer, or it has to allow sharing with us. 2593 $condition = $tokens[$tokens[$i]['scope_condition']]['code']; 2594 if ($condition !== $oldCondition) { 2595 if (isset($tokenizer->scopeOpeners[$oldCondition]['with'][$condition]) === false) { 2596 $badToken = $tokens[$oldOpener]['scope_condition']; 2597 2598 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2599 $type = token_name($oldCondition); 2600 echo str_repeat("\t", ($level + 1)); 2601 echo "* scope closer was bad, cleaning up $badToken:$type *".PHP_EOL; 2602 } 2603 2604 for ($x = ($oldOpener + 1); $x <= $i; $x++) { 2605 $oldConditions = $tokens[$x]['conditions']; 2606 $oldLevel = $tokens[$x]['level']; 2607 $tokens[$x]['level']--; 2608 unset($tokens[$x]['conditions'][$badToken]); 2609 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2610 $type = $tokens[$x]['type']; 2611 $oldConds = ''; 2612 foreach ($oldConditions as $condition) { 2613 $oldConds .= token_name($condition).','; 2614 } 2615 2616 $oldConds = rtrim($oldConds, ','); 2617 2618 $newConds = ''; 2619 foreach ($tokens[$x]['conditions'] as $condition) { 2620 $newConds .= token_name($condition).','; 2621 } 2622 2623 $newConds = rtrim($newConds, ','); 2624 2625 $newLevel = $tokens[$x]['level']; 2626 echo str_repeat("\t", ($level + 1)); 2627 echo "* cleaned $x:$type *".PHP_EOL; 2628 echo str_repeat("\t", ($level + 2)); 2629 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL; 2630 echo str_repeat("\t", ($level + 2)); 2631 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL; 2632 }//end if 2633 }//end for 2634 }//end if 2635 }//end if 2636 2637 $level--; 2638 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2639 echo str_repeat("\t", ($level + 2)); 2640 echo '* level decreased *'.PHP_EOL; 2641 } 2642 2643 $tokens[$i]['level'] = $level; 2644 $tokens[$i]['conditions'] = $conditions; 2645 }//end if 2646 }//end foreach 2647 }//end if 2648 }//end if 2649 }//end for 2650 2651 if (PHP_CODESNIFFER_VERBOSITY > 1) { 2652 echo "\t*** END LEVEL MAP ***".PHP_EOL; 2653 } 2654 2655 }//end _createLevelMap() 2656 2657 2658 /** 2659 * Returns the declaration names for classes, interfaces, and functions. 2660 * 2661 * @param int $stackPtr The position of the declaration token which 2662 * declared the class, interface, trait or function. 2663 * 2664 * @return string|null The name of the class, interface or function. 2665 * or NULL if the function or class is anonymous. 2666 * @throws PHP_CodeSniffer_Exception If the specified token is not of type 2667 * T_FUNCTION, T_CLASS, T_ANON_CLASS, 2668 * T_TRAIT or T_INTERFACE. 2669 */ 2670 public function getDeclarationName($stackPtr) 2671 { 2672 $tokenCode = $this->_tokens[$stackPtr]['code']; 2673 2674 if ($tokenCode === T_ANON_CLASS) { 2675 return null; 2676 } 2677 2678 if ($tokenCode === T_CLOSURE) { 2679 return null; 2680 } 2681 2682 if ($tokenCode !== T_FUNCTION 2683 && $tokenCode !== T_CLASS 2684 && $tokenCode !== T_INTERFACE 2685 && $tokenCode !== T_TRAIT 2686 ) { 2687 throw new PHP_CodeSniffer_Exception('Token type "'.$this->_tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT'); 2688 } 2689 2690 $content = null; 2691 for ($i = $stackPtr; $i < $this->numTokens; $i++) { 2692 if ($this->_tokens[$i]['code'] === T_STRING) { 2693 $content = $this->_tokens[$i]['content']; 2694 break; 2695 } 2696 } 2697 2698 return $content; 2699 2700 }//end getDeclarationName() 2701 2702 2703 /** 2704 * Check if the token at the specified position is a anonymous function. 2705 * 2706 * @param int $stackPtr The position of the declaration token which 2707 * declared the class, interface or function. 2708 * 2709 * @return boolean 2710 * @throws PHP_CodeSniffer_Exception If the specified token is not of type 2711 * T_FUNCTION 2712 */ 2713 public function isAnonymousFunction($stackPtr) 2714 { 2715 $tokenCode = $this->_tokens[$stackPtr]['code']; 2716 if ($tokenCode !== T_FUNCTION) { 2717 throw new PHP_CodeSniffer_Exception('Token type is not T_FUNCTION'); 2718 } 2719 2720 if (isset($this->_tokens[$stackPtr]['parenthesis_opener']) === false) { 2721 // Something is not right with this function. 2722 return false; 2723 } 2724 2725 $name = false; 2726 for ($i = ($stackPtr + 1); $i < $this->numTokens; $i++) { 2727 if ($this->_tokens[$i]['code'] === T_STRING) { 2728 $name = $i; 2729 break; 2730 } 2731 } 2732 2733 if ($name === false) { 2734 // No name found. 2735 return true; 2736 } 2737 2738 $open = $this->_tokens[$stackPtr]['parenthesis_opener']; 2739 if ($name > $open) { 2740 return true; 2741 } 2742 2743 return false; 2744 2745 }//end isAnonymousFunction() 2746 2747 2748 /** 2749 * Returns the method parameters for the specified function token. 2750 * 2751 * Each parameter is in the following format: 2752 * 2753 * <code> 2754 * 0 => array( 2755 * 'token' => int, // The position of the var in the token stack. 2756 * 'name' => '$var', // The variable name. 2757 * 'content' => string, // The full content of the variable definition. 2758 * 'pass_by_reference' => boolean, // Is the variable passed by reference? 2759 * 'variable_length' => boolean, // Is the param of variable length through use of `...` ? 2760 * 'type_hint' => string, // The type hint for the variable. 2761 * 'nullable_type' => boolean, // Is the variable using a nullable type? 2762 * ) 2763 * </code> 2764 * 2765 * Parameters with default values have an additional array index of 2766 * 'default' with the value of the default as a string. 2767 * 2768 * @param int $stackPtr The position in the stack of the function token 2769 * to acquire the parameters for. 2770 * 2771 * @return array 2772 * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of 2773 * type T_FUNCTION or T_CLOSURE. 2774 */ 2775 public function getMethodParameters($stackPtr) 2776 { 2777 if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION 2778 && $this->_tokens[$stackPtr]['code'] !== T_CLOSURE 2779 ) { 2780 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION or T_CLOSURE'); 2781 } 2782 2783 $opener = $this->_tokens[$stackPtr]['parenthesis_opener']; 2784 $closer = $this->_tokens[$stackPtr]['parenthesis_closer']; 2785 2786 $vars = array(); 2787 $currVar = null; 2788 $paramStart = ($opener + 1); 2789 $defaultStart = null; 2790 $paramCount = 0; 2791 $passByReference = false; 2792 $variableLength = false; 2793 $typeHint = ''; 2794 $nullableType = false; 2795 2796 for ($i = $paramStart; $i <= $closer; $i++) { 2797 // Check to see if this token has a parenthesis or bracket opener. If it does 2798 // it's likely to be an array which might have arguments in it. This 2799 // could cause problems in our parsing below, so lets just skip to the 2800 // end of it. 2801 if (isset($this->_tokens[$i]['parenthesis_opener']) === true) { 2802 // Don't do this if it's the close parenthesis for the method. 2803 if ($i !== $this->_tokens[$i]['parenthesis_closer']) { 2804 $i = ($this->_tokens[$i]['parenthesis_closer'] + 1); 2805 } 2806 } 2807 2808 if (isset($this->_tokens[$i]['bracket_opener']) === true) { 2809 // Don't do this if it's the close parenthesis for the method. 2810 if ($i !== $this->_tokens[$i]['bracket_closer']) { 2811 $i = ($this->_tokens[$i]['bracket_closer'] + 1); 2812 } 2813 } 2814 2815 switch ($this->_tokens[$i]['code']) { 2816 case T_BITWISE_AND: 2817 $passByReference = true; 2818 break; 2819 case T_VARIABLE: 2820 $currVar = $i; 2821 break; 2822 case T_ELLIPSIS: 2823 $variableLength = true; 2824 break; 2825 case T_ARRAY_HINT: 2826 case T_CALLABLE: 2827 $typeHint .= $this->_tokens[$i]['content']; 2828 break; 2829 case T_SELF: 2830 case T_PARENT: 2831 case T_STATIC: 2832 // Self is valid, the others invalid, but were probably intended as type hints. 2833 if (isset($defaultStart) === false) { 2834 $typeHint .= $this->_tokens[$i]['content']; 2835 } 2836 break; 2837 case T_STRING: 2838 // This is a string, so it may be a type hint, but it could 2839 // also be a constant used as a default value. 2840 $prevComma = false; 2841 for ($t = $i; $t >= $opener; $t--) { 2842 if ($this->_tokens[$t]['code'] === T_COMMA) { 2843 $prevComma = $t; 2844 break; 2845 } 2846 } 2847 2848 if ($prevComma !== false) { 2849 $nextEquals = false; 2850 for ($t = $prevComma; $t < $i; $t++) { 2851 if ($this->_tokens[$t]['code'] === T_EQUAL) { 2852 $nextEquals = $t; 2853 break; 2854 } 2855 } 2856 2857 if ($nextEquals !== false) { 2858 break; 2859 } 2860 } 2861 2862 if ($defaultStart === null) { 2863 $typeHint .= $this->_tokens[$i]['content']; 2864 } 2865 break; 2866 case T_NS_SEPARATOR: 2867 // Part of a type hint or default value. 2868 if ($defaultStart === null) { 2869 $typeHint .= $this->_tokens[$i]['content']; 2870 } 2871 break; 2872 case T_NULLABLE: 2873 if ($defaultStart === null) { 2874 $nullableType = true; 2875 $typeHint .= $this->_tokens[$i]['content']; 2876 } 2877 break; 2878 case T_CLOSE_PARENTHESIS: 2879 case T_COMMA: 2880 // If it's null, then there must be no parameters for this 2881 // method. 2882 if ($currVar === null) { 2883 continue; 2884 } 2885 2886 $vars[$paramCount] = array(); 2887 $vars[$paramCount]['token'] = $currVar; 2888 $vars[$paramCount]['name'] = $this->_tokens[$currVar]['content']; 2889 $vars[$paramCount]['content'] = trim($this->getTokensAsString($paramStart, ($i - $paramStart))); 2890 2891 if ($defaultStart !== null) { 2892 $vars[$paramCount]['default'] = trim($this->getTokensAsString($defaultStart, ($i - $defaultStart))); 2893 } 2894 2895 $vars[$paramCount]['pass_by_reference'] = $passByReference; 2896 $vars[$paramCount]['variable_length'] = $variableLength; 2897 $vars[$paramCount]['type_hint'] = $typeHint; 2898 $vars[$paramCount]['nullable_type'] = $nullableType; 2899 2900 // Reset the vars, as we are about to process the next parameter. 2901 $defaultStart = null; 2902 $paramStart = ($i + 1); 2903 $passByReference = false; 2904 $variableLength = false; 2905 $typeHint = ''; 2906 $nullableType = false; 2907 2908 $paramCount++; 2909 break; 2910 case T_EQUAL: 2911 $defaultStart = ($i + 1); 2912 break; 2913 }//end switch 2914 }//end for 2915 2916 return $vars; 2917 2918 }//end getMethodParameters() 2919 2920 2921 /** 2922 * Returns the visibility and implementation properties of a method. 2923 * 2924 * The format of the array is: 2925 * <code> 2926 * array( 2927 * 'scope' => 'public', // public private or protected 2928 * 'scope_specified' => true, // true is scope keyword was found. 2929 * 'is_abstract' => false, // true if the abstract keyword was found. 2930 * 'is_final' => false, // true if the final keyword was found. 2931 * 'is_static' => false, // true if the static keyword was found. 2932 * 'is_closure' => false, // true if no name is found. 2933 * ); 2934 * </code> 2935 * 2936 * @param int $stackPtr The position in the stack of the T_FUNCTION token to 2937 * acquire the properties for. 2938 * 2939 * @return array 2940 * @throws PHP_CodeSniffer_Exception If the specified position is not a 2941 * T_FUNCTION token. 2942 */ 2943 public function getMethodProperties($stackPtr) 2944 { 2945 if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) { 2946 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION'); 2947 } 2948 2949 $valid = array( 2950 T_PUBLIC => T_PUBLIC, 2951 T_PRIVATE => T_PRIVATE, 2952 T_PROTECTED => T_PROTECTED, 2953 T_STATIC => T_STATIC, 2954 T_FINAL => T_FINAL, 2955 T_ABSTRACT => T_ABSTRACT, 2956 T_WHITESPACE => T_WHITESPACE, 2957 T_COMMENT => T_COMMENT, 2958 T_DOC_COMMENT => T_DOC_COMMENT, 2959 ); 2960 2961 $scope = 'public'; 2962 $scopeSpecified = false; 2963 $isAbstract = false; 2964 $isFinal = false; 2965 $isStatic = false; 2966 $isClosure = $this->isAnonymousFunction($stackPtr); 2967 2968 for ($i = ($stackPtr - 1); $i > 0; $i--) { 2969 if (isset($valid[$this->_tokens[$i]['code']]) === false) { 2970 break; 2971 } 2972 2973 switch ($this->_tokens[$i]['code']) { 2974 case T_PUBLIC: 2975 $scope = 'public'; 2976 $scopeSpecified = true; 2977 break; 2978 case T_PRIVATE: 2979 $scope = 'private'; 2980 $scopeSpecified = true; 2981 break; 2982 case T_PROTECTED: 2983 $scope = 'protected'; 2984 $scopeSpecified = true; 2985 break; 2986 case T_ABSTRACT: 2987 $isAbstract = true; 2988 break; 2989 case T_FINAL: 2990 $isFinal = true; 2991 break; 2992 case T_STATIC: 2993 $isStatic = true; 2994 break; 2995 }//end switch 2996 }//end for 2997 2998 return array( 2999 'scope' => $scope, 3000 'scope_specified' => $scopeSpecified, 3001 'is_abstract' => $isAbstract, 3002 'is_final' => $isFinal, 3003 'is_static' => $isStatic, 3004 'is_closure' => $isClosure, 3005 ); 3006 3007 }//end getMethodProperties() 3008 3009 3010 /** 3011 * Returns the visibility and implementation properties of the class member 3012 * variable found at the specified position in the stack. 3013 * 3014 * The format of the array is: 3015 * 3016 * <code> 3017 * array( 3018 * 'scope' => 'public', // public private or protected 3019 * 'is_static' => false, // true if the static keyword was found. 3020 * ); 3021 * </code> 3022 * 3023 * @param int $stackPtr The position in the stack of the T_VARIABLE token to 3024 * acquire the properties for. 3025 * 3026 * @return array 3027 * @throws PHP_CodeSniffer_Exception If the specified position is not a 3028 * T_VARIABLE token, or if the position is not 3029 * a class member variable. 3030 */ 3031 public function getMemberProperties($stackPtr) 3032 { 3033 if ($this->_tokens[$stackPtr]['code'] !== T_VARIABLE) { 3034 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_VARIABLE'); 3035 } 3036 3037 $conditions = array_keys($this->_tokens[$stackPtr]['conditions']); 3038 $ptr = array_pop($conditions); 3039 if (isset($this->_tokens[$ptr]) === false 3040 || ($this->_tokens[$ptr]['code'] !== T_CLASS 3041 && $this->_tokens[$ptr]['code'] !== T_ANON_CLASS 3042 && $this->_tokens[$ptr]['code'] !== T_TRAIT) 3043 ) { 3044 if (isset($this->_tokens[$ptr]) === true 3045 && $this->_tokens[$ptr]['code'] === T_INTERFACE 3046 ) { 3047 // T_VARIABLEs in interfaces can actually be method arguments 3048 // but they wont be seen as being inside the method because there 3049 // are no scope openers and closers for abstract methods. If it is in 3050 // parentheses, we can be pretty sure it is a method argument. 3051 if (isset($this->_tokens[$stackPtr]['nested_parenthesis']) === false 3052 || empty($this->_tokens[$stackPtr]['nested_parenthesis']) === true 3053 ) { 3054 $error = 'Possible parse error: interfaces may not include member vars'; 3055 $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar'); 3056 return array(); 3057 } 3058 } else { 3059 throw new PHP_CodeSniffer_Exception('$stackPtr is not a class member var'); 3060 } 3061 } 3062 3063 $valid = array( 3064 T_PUBLIC => T_PUBLIC, 3065 T_PRIVATE => T_PRIVATE, 3066 T_PROTECTED => T_PROTECTED, 3067 T_STATIC => T_STATIC, 3068 T_WHITESPACE => T_WHITESPACE, 3069 T_COMMENT => T_COMMENT, 3070 T_DOC_COMMENT => T_DOC_COMMENT, 3071 T_VARIABLE => T_VARIABLE, 3072 T_COMMA => T_COMMA, 3073 ); 3074 3075 $scope = 'public'; 3076 $scopeSpecified = false; 3077 $isStatic = false; 3078 3079 for ($i = ($stackPtr - 1); $i > 0; $i--) { 3080 if (isset($valid[$this->_tokens[$i]['code']]) === false) { 3081 break; 3082 } 3083 3084 switch ($this->_tokens[$i]['code']) { 3085 case T_PUBLIC: 3086 $scope = 'public'; 3087 $scopeSpecified = true; 3088 break; 3089 case T_PRIVATE: 3090 $scope = 'private'; 3091 $scopeSpecified = true; 3092 break; 3093 case T_PROTECTED: 3094 $scope = 'protected'; 3095 $scopeSpecified = true; 3096 break; 3097 case T_STATIC: 3098 $isStatic = true; 3099 break; 3100 } 3101 }//end for 3102 3103 return array( 3104 'scope' => $scope, 3105 'scope_specified' => $scopeSpecified, 3106 'is_static' => $isStatic, 3107 ); 3108 3109 }//end getMemberProperties() 3110 3111 3112 /** 3113 * Returns the visibility and implementation properties of a class. 3114 * 3115 * The format of the array is: 3116 * <code> 3117 * array( 3118 * 'is_abstract' => false, // true if the abstract keyword was found. 3119 * 'is_final' => false, // true if the final keyword was found. 3120 * ); 3121 * </code> 3122 * 3123 * @param int $stackPtr The position in the stack of the T_CLASS token to 3124 * acquire the properties for. 3125 * 3126 * @return array 3127 * @throws PHP_CodeSniffer_Exception If the specified position is not a 3128 * T_CLASS token. 3129 */ 3130 public function getClassProperties($stackPtr) 3131 { 3132 if ($this->_tokens[$stackPtr]['code'] !== T_CLASS) { 3133 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_CLASS'); 3134 } 3135 3136 $valid = array( 3137 T_FINAL => T_FINAL, 3138 T_ABSTRACT => T_ABSTRACT, 3139 T_WHITESPACE => T_WHITESPACE, 3140 T_COMMENT => T_COMMENT, 3141 T_DOC_COMMENT => T_DOC_COMMENT, 3142 ); 3143 3144 $isAbstract = false; 3145 $isFinal = false; 3146 3147 for ($i = ($stackPtr - 1); $i > 0; $i--) { 3148 if (isset($valid[$this->_tokens[$i]['code']]) === false) { 3149 break; 3150 } 3151 3152 switch ($this->_tokens[$i]['code']) { 3153 case T_ABSTRACT: 3154 $isAbstract = true; 3155 break; 3156 3157 case T_FINAL: 3158 $isFinal = true; 3159 break; 3160 } 3161 }//end for 3162 3163 return array( 3164 'is_abstract' => $isAbstract, 3165 'is_final' => $isFinal, 3166 ); 3167 3168 }//end getClassProperties() 3169 3170 3171 /** 3172 * Determine if the passed token is a reference operator. 3173 * 3174 * Returns true if the specified token position represents a reference. 3175 * Returns false if the token represents a bitwise operator. 3176 * 3177 * @param int $stackPtr The position of the T_BITWISE_AND token. 3178 * 3179 * @return boolean 3180 */ 3181 public function isReference($stackPtr) 3182 { 3183 if ($this->_tokens[$stackPtr]['code'] !== T_BITWISE_AND) { 3184 return false; 3185 } 3186 3187 $tokenBefore = $this->findPrevious( 3188 PHP_CodeSniffer_Tokens::$emptyTokens, 3189 ($stackPtr - 1), 3190 null, 3191 true 3192 ); 3193 3194 if ($this->_tokens[$tokenBefore]['code'] === T_FUNCTION) { 3195 // Function returns a reference. 3196 return true; 3197 } 3198 3199 if ($this->_tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) { 3200 // Inside a foreach loop, this is a reference. 3201 return true; 3202 } 3203 3204 if ($this->_tokens[$tokenBefore]['code'] === T_AS) { 3205 // Inside a foreach loop, this is a reference. 3206 return true; 3207 } 3208 3209 if ($this->_tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY) { 3210 // Inside an array declaration, this is a reference. 3211 return true; 3212 } 3213 3214 if (isset(PHP_CodeSniffer_Tokens::$assignmentTokens[$this->_tokens[$tokenBefore]['code']]) === true) { 3215 // This is directly after an assignment. It's a reference. Even if 3216 // it is part of an operation, the other tests will handle it. 3217 return true; 3218 } 3219 3220 if (isset($this->_tokens[$stackPtr]['nested_parenthesis']) === true) { 3221 $brackets = $this->_tokens[$stackPtr]['nested_parenthesis']; 3222 $lastBracket = array_pop($brackets); 3223 if (isset($this->_tokens[$lastBracket]['parenthesis_owner']) === true) { 3224 $owner = $this->_tokens[$this->_tokens[$lastBracket]['parenthesis_owner']]; 3225 if ($owner['code'] === T_FUNCTION 3226 || $owner['code'] === T_CLOSURE 3227 || $owner['code'] === T_ARRAY 3228 ) { 3229 // Inside a function or array declaration, this is a reference. 3230 return true; 3231 } 3232 } else { 3233 $prev = false; 3234 for ($t = ($this->_tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) { 3235 if ($this->_tokens[$t]['code'] !== T_WHITESPACE) { 3236 $prev = $t; 3237 break; 3238 } 3239 } 3240 3241 if ($prev !== false && $this->_tokens[$prev]['code'] === T_USE) { 3242 return true; 3243 } 3244 }//end if 3245 }//end if 3246 3247 $tokenAfter = $this->findNext( 3248 PHP_CodeSniffer_Tokens::$emptyTokens, 3249 ($stackPtr + 1), 3250 null, 3251 true 3252 ); 3253 3254 if ($this->_tokens[$tokenAfter]['code'] === T_VARIABLE 3255 && ($this->_tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS 3256 || $this->_tokens[$tokenBefore]['code'] === T_COMMA) 3257 ) { 3258 return true; 3259 } 3260 3261 return false; 3262 3263 }//end isReference() 3264 3265 3266 /** 3267 * Returns the content of the tokens from the specified start position in 3268 * the token stack for the specified length. 3269 * 3270 * @param int $start The position to start from in the token stack. 3271 * @param int $length The length of tokens to traverse from the start pos. 3272 * 3273 * @return string The token contents. 3274 */ 3275 public function getTokensAsString($start, $length) 3276 { 3277 $str = ''; 3278 $end = ($start + $length); 3279 if ($end > $this->numTokens) { 3280 $end = $this->numTokens; 3281 } 3282 3283 for ($i = $start; $i < $end; $i++) { 3284 $str .= $this->_tokens[$i]['content']; 3285 } 3286 3287 return $str; 3288 3289 }//end getTokensAsString() 3290 3291 3292 /** 3293 * Returns the position of the previous specified token(s). 3294 * 3295 * If a value is specified, the previous token of the specified type(s) 3296 * containing the specified value will be returned. 3297 * 3298 * Returns false if no token can be found. 3299 * 3300 * @param int|array $types The type(s) of tokens to search for. 3301 * @param int $start The position to start searching from in the 3302 * token stack. 3303 * @param int $end The end position to fail if no token is found. 3304 * if not specified or null, end will default to 3305 * the start of the token stack. 3306 * @param bool $exclude If true, find the previous token that are NOT of 3307 * the types specified in $types. 3308 * @param string $value The value that the token(s) must be equal to. 3309 * If value is omitted, tokens with any value will 3310 * be returned. 3311 * @param bool $local If true, tokens outside the current statement 3312 * will not be checked. IE. checking will stop 3313 * at the previous semi-colon found. 3314 * 3315 * @return int|bool 3316 * @see findNext() 3317 */ 3318 public function findPrevious( 3319 $types, 3320 $start, 3321 $end=null, 3322 $exclude=false, 3323 $value=null, 3324 $local=false 3325 ) { 3326 $types = (array) $types; 3327 3328 if ($end === null) { 3329 $end = 0; 3330 } 3331 3332 for ($i = $start; $i >= $end; $i--) { 3333 $found = (bool) $exclude; 3334 foreach ($types as $type) { 3335 if ($this->_tokens[$i]['code'] === $type) { 3336 $found = !$exclude; 3337 break; 3338 } 3339 } 3340 3341 if ($found === true) { 3342 if ($value === null) { 3343 return $i; 3344 } else if ($this->_tokens[$i]['content'] === $value) { 3345 return $i; 3346 } 3347 } 3348 3349 if ($local === true) { 3350 if (isset($this->_tokens[$i]['scope_opener']) === true 3351 && $i === $this->_tokens[$i]['scope_closer'] 3352 ) { 3353 $i = $this->_tokens[$i]['scope_opener']; 3354 } else if (isset($this->_tokens[$i]['bracket_opener']) === true 3355 && $i === $this->_tokens[$i]['bracket_closer'] 3356 ) { 3357 $i = $this->_tokens[$i]['bracket_opener']; 3358 } else if (isset($this->_tokens[$i]['parenthesis_opener']) === true 3359 && $i === $this->_tokens[$i]['parenthesis_closer'] 3360 ) { 3361 $i = $this->_tokens[$i]['parenthesis_opener']; 3362 } else if ($this->_tokens[$i]['code'] === T_SEMICOLON) { 3363 break; 3364 } 3365 } 3366 }//end for 3367 3368 return false; 3369 3370 }//end findPrevious() 3371 3372 3373 /** 3374 * Returns the position of the next specified token(s). 3375 * 3376 * If a value is specified, the next token of the specified type(s) 3377 * containing the specified value will be returned. 3378 * 3379 * Returns false if no token can be found. 3380 * 3381 * @param int|array $types The type(s) of tokens to search for. 3382 * @param int $start The position to start searching from in the 3383 * token stack. 3384 * @param int $end The end position to fail if no token is found. 3385 * if not specified or null, end will default to 3386 * the end of the token stack. 3387 * @param bool $exclude If true, find the next token that is NOT of 3388 * a type specified in $types. 3389 * @param string $value The value that the token(s) must be equal to. 3390 * If value is omitted, tokens with any value will 3391 * be returned. 3392 * @param bool $local If true, tokens outside the current statement 3393 * will not be checked. i.e., checking will stop 3394 * at the next semi-colon found. 3395 * 3396 * @return int|bool 3397 * @see findPrevious() 3398 */ 3399 public function findNext( 3400 $types, 3401 $start, 3402 $end=null, 3403 $exclude=false, 3404 $value=null, 3405 $local=false 3406 ) { 3407 $types = (array) $types; 3408 3409 if ($end === null || $end > $this->numTokens) { 3410 $end = $this->numTokens; 3411 } 3412 3413 for ($i = $start; $i < $end; $i++) { 3414 $found = (bool) $exclude; 3415 foreach ($types as $type) { 3416 if ($this->_tokens[$i]['code'] === $type) { 3417 $found = !$exclude; 3418 break; 3419 } 3420 } 3421 3422 if ($found === true) { 3423 if ($value === null) { 3424 return $i; 3425 } else if ($this->_tokens[$i]['content'] === $value) { 3426 return $i; 3427 } 3428 } 3429 3430 if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) { 3431 break; 3432 } 3433 }//end for 3434 3435 return false; 3436 3437 }//end findNext() 3438 3439 3440 /** 3441 * Returns the position of the first non-whitespace token in a statement. 3442 * 3443 * @param int $start The position to start searching from in the token stack. 3444 * @param int|array $ignore Token types that should not be considered stop points. 3445 * 3446 * @return int 3447 */ 3448 public function findStartOfStatement($start, $ignore=null) 3449 { 3450 $endTokens = PHP_CodeSniffer_Tokens::$blockOpeners; 3451 3452 $endTokens[T_COLON] = true; 3453 $endTokens[T_COMMA] = true; 3454 $endTokens[T_DOUBLE_ARROW] = true; 3455 $endTokens[T_SEMICOLON] = true; 3456 $endTokens[T_OPEN_TAG] = true; 3457 $endTokens[T_CLOSE_TAG] = true; 3458 $endTokens[T_OPEN_SHORT_ARRAY] = true; 3459 3460 if ($ignore !== null) { 3461 $ignore = (array) $ignore; 3462 foreach ($ignore as $code) { 3463 if (isset($endTokens[$code]) === true) { 3464 unset($endTokens[$code]); 3465 } 3466 } 3467 } 3468 3469 $lastNotEmpty = $start; 3470 3471 for ($i = $start; $i >= 0; $i--) { 3472 if (isset($endTokens[$this->_tokens[$i]['code']]) === true) { 3473 // Found the end of the previous statement. 3474 return $lastNotEmpty; 3475 } 3476 3477 if (isset($this->_tokens[$i]['scope_opener']) === true 3478 && $i === $this->_tokens[$i]['scope_closer'] 3479 ) { 3480 // Found the end of the previous scope block. 3481 return $lastNotEmpty; 3482 } 3483 3484 // Skip nested statements. 3485 if (isset($this->_tokens[$i]['bracket_opener']) === true 3486 && $i === $this->_tokens[$i]['bracket_closer'] 3487 ) { 3488 $i = $this->_tokens[$i]['bracket_opener']; 3489 } else if (isset($this->_tokens[$i]['parenthesis_opener']) === true 3490 && $i === $this->_tokens[$i]['parenthesis_closer'] 3491 ) { 3492 $i = $this->_tokens[$i]['parenthesis_opener']; 3493 } 3494 3495 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$this->_tokens[$i]['code']]) === false) { 3496 $lastNotEmpty = $i; 3497 } 3498 }//end for 3499 3500 return 0; 3501 3502 }//end findStartOfStatement() 3503 3504 3505 /** 3506 * Returns the position of the last non-whitespace token in a statement. 3507 * 3508 * @param int $start The position to start searching from in the token stack. 3509 * @param int|array $ignore Token types that should not be considered stop points. 3510 * 3511 * @return int 3512 */ 3513 public function findEndOfStatement($start, $ignore=null) 3514 { 3515 $endTokens = array( 3516 T_COLON => true, 3517 T_COMMA => true, 3518 T_DOUBLE_ARROW => true, 3519 T_SEMICOLON => true, 3520 T_CLOSE_PARENTHESIS => true, 3521 T_CLOSE_SQUARE_BRACKET => true, 3522 T_CLOSE_CURLY_BRACKET => true, 3523 T_CLOSE_SHORT_ARRAY => true, 3524 T_OPEN_TAG => true, 3525 T_CLOSE_TAG => true, 3526 ); 3527 3528 if ($ignore !== null) { 3529 $ignore = (array) $ignore; 3530 foreach ($ignore as $code) { 3531 if (isset($endTokens[$code]) === true) { 3532 unset($endTokens[$code]); 3533 } 3534 } 3535 } 3536 3537 $lastNotEmpty = $start; 3538 3539 for ($i = $start; $i < $this->numTokens; $i++) { 3540 if ($i !== $start && isset($endTokens[$this->_tokens[$i]['code']]) === true) { 3541 // Found the end of the statement. 3542 if ($this->_tokens[$i]['code'] === T_CLOSE_PARENTHESIS 3543 || $this->_tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET 3544 || $this->_tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET 3545 || $this->_tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY 3546 || $this->_tokens[$i]['code'] === T_OPEN_TAG 3547 || $this->_tokens[$i]['code'] === T_CLOSE_TAG 3548 ) { 3549 return $lastNotEmpty; 3550 } 3551 3552 return $i; 3553 } 3554 3555 // Skip nested statements. 3556 if (isset($this->_tokens[$i]['scope_closer']) === true 3557 && ($i === $this->_tokens[$i]['scope_opener'] 3558 || $i === $this->_tokens[$i]['scope_condition']) 3559 ) { 3560 $i = $this->_tokens[$i]['scope_closer']; 3561 } else if (isset($this->_tokens[$i]['bracket_closer']) === true 3562 && $i === $this->_tokens[$i]['bracket_opener'] 3563 ) { 3564 $i = $this->_tokens[$i]['bracket_closer']; 3565 } else if (isset($this->_tokens[$i]['parenthesis_closer']) === true 3566 && $i === $this->_tokens[$i]['parenthesis_opener'] 3567 ) { 3568 $i = $this->_tokens[$i]['parenthesis_closer']; 3569 } 3570 3571 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$this->_tokens[$i]['code']]) === false) { 3572 $lastNotEmpty = $i; 3573 } 3574 }//end for 3575 3576 return ($this->numTokens - 1); 3577 3578 }//end findEndOfStatement() 3579 3580 3581 /** 3582 * Returns the position of the first token on a line, matching given type. 3583 * 3584 * Returns false if no token can be found. 3585 * 3586 * @param int|array $types The type(s) of tokens to search for. 3587 * @param int $start The position to start searching from in the 3588 * token stack. The first token matching on 3589 * this line before this token will be returned. 3590 * @param bool $exclude If true, find the token that is NOT of 3591 * the types specified in $types. 3592 * @param string $value The value that the token must be equal to. 3593 * If value is omitted, tokens with any value will 3594 * be returned. 3595 * 3596 * @return int | bool 3597 */ 3598 public function findFirstOnLine($types, $start, $exclude=false, $value=null) 3599 { 3600 if (is_array($types) === false) { 3601 $types = array($types); 3602 } 3603 3604 $foundToken = false; 3605 3606 for ($i = $start; $i >= 0; $i--) { 3607 if ($this->_tokens[$i]['line'] < $this->_tokens[$start]['line']) { 3608 break; 3609 } 3610 3611 $found = $exclude; 3612 foreach ($types as $type) { 3613 if ($exclude === false) { 3614 if ($this->_tokens[$i]['code'] === $type) { 3615 $found = true; 3616 break; 3617 } 3618 } else { 3619 if ($this->_tokens[$i]['code'] === $type) { 3620 $found = false; 3621 break; 3622 } 3623 } 3624 } 3625 3626 if ($found === true) { 3627 if ($value === null) { 3628 $foundToken = $i; 3629 } else if ($this->_tokens[$i]['content'] === $value) { 3630 $foundToken = $i; 3631 } 3632 } 3633 }//end for 3634 3635 return $foundToken; 3636 3637 }//end findFirstOnLine() 3638 3639 3640 /** 3641 * Determine if the passed token has a condition of one of the passed types. 3642 * 3643 * @param int $stackPtr The position of the token we are checking. 3644 * @param int|array $types The type(s) of tokens to search for. 3645 * 3646 * @return boolean 3647 */ 3648 public function hasCondition($stackPtr, $types) 3649 { 3650 // Check for the existence of the token. 3651 if (isset($this->_tokens[$stackPtr]) === false) { 3652 return false; 3653 } 3654 3655 // Make sure the token has conditions. 3656 if (isset($this->_tokens[$stackPtr]['conditions']) === false) { 3657 return false; 3658 } 3659 3660 $types = (array) $types; 3661 $conditions = $this->_tokens[$stackPtr]['conditions']; 3662 3663 foreach ($types as $type) { 3664 if (in_array($type, $conditions) === true) { 3665 // We found a token with the required type. 3666 return true; 3667 } 3668 } 3669 3670 return false; 3671 3672 }//end hasCondition() 3673 3674 3675 /** 3676 * Return the position of the condition for the passed token. 3677 * 3678 * Returns FALSE if the token does not have the condition. 3679 * 3680 * @param int $stackPtr The position of the token we are checking. 3681 * @param int $type The type of token to search for. 3682 * 3683 * @return int 3684 */ 3685 public function getCondition($stackPtr, $type) 3686 { 3687 // Check for the existence of the token. 3688 if (isset($this->_tokens[$stackPtr]) === false) { 3689 return false; 3690 } 3691 3692 // Make sure the token has conditions. 3693 if (isset($this->_tokens[$stackPtr]['conditions']) === false) { 3694 return false; 3695 } 3696 3697 $conditions = $this->_tokens[$stackPtr]['conditions']; 3698 foreach ($conditions as $token => $condition) { 3699 if ($condition === $type) { 3700 return $token; 3701 } 3702 } 3703 3704 return false; 3705 3706 }//end getCondition() 3707 3708 3709 /** 3710 * Returns the name of the class that the specified class extends. 3711 * 3712 * Returns FALSE on error or if there is no extended class name. 3713 * 3714 * @param int $stackPtr The stack position of the class. 3715 * 3716 * @return string 3717 */ 3718 public function findExtendedClassName($stackPtr) 3719 { 3720 // Check for the existence of the token. 3721 if (isset($this->_tokens[$stackPtr]) === false) { 3722 return false; 3723 } 3724 3725 if ($this->_tokens[$stackPtr]['code'] !== T_CLASS 3726 && $this->_tokens[$stackPtr]['code'] !== T_ANON_CLASS 3727 ) { 3728 return false; 3729 } 3730 3731 if (isset($this->_tokens[$stackPtr]['scope_closer']) === false) { 3732 return false; 3733 } 3734 3735 $classCloserIndex = $this->_tokens[$stackPtr]['scope_closer']; 3736 $extendsIndex = $this->findNext(T_EXTENDS, $stackPtr, $classCloserIndex); 3737 if (false === $extendsIndex) { 3738 return false; 3739 } 3740 3741 $find = array( 3742 T_NS_SEPARATOR, 3743 T_STRING, 3744 T_WHITESPACE, 3745 ); 3746 3747 $end = $this->findNext($find, ($extendsIndex + 1), $classCloserIndex, true); 3748 $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1)); 3749 $name = trim($name); 3750 3751 if ($name === '') { 3752 return false; 3753 } 3754 3755 return $name; 3756 3757 }//end findExtendedClassName() 3758 3759 3760 /** 3761 * Returns the name(s) of the interface(s) that the specified class implements. 3762 * 3763 * Returns FALSE on error or if there are no implemented interface names. 3764 * 3765 * @param int $stackPtr The stack position of the class. 3766 * 3767 * @return array|false 3768 */ 3769 public function findImplementedInterfaceNames($stackPtr) 3770 { 3771 // Check for the existence of the token. 3772 if (isset($this->_tokens[$stackPtr]) === false) { 3773 return false; 3774 } 3775 3776 if ($this->_tokens[$stackPtr]['code'] !== T_CLASS 3777 && $this->_tokens[$stackPtr]['code'] !== T_ANON_CLASS 3778 ) { 3779 return false; 3780 } 3781 3782 if (isset($this->_tokens[$stackPtr]['scope_closer']) === false) { 3783 return false; 3784 } 3785 3786 $classOpenerIndex = $this->_tokens[$stackPtr]['scope_opener']; 3787 $implementsIndex = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex); 3788 if ($implementsIndex === false) { 3789 return false; 3790 } 3791 3792 $find = array( 3793 T_NS_SEPARATOR, 3794 T_STRING, 3795 T_WHITESPACE, 3796 T_COMMA, 3797 ); 3798 3799 $end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true); 3800 $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1)); 3801 $name = trim($name); 3802 3803 if ($name === '') { 3804 return false; 3805 } else { 3806 $names = explode(',', $name); 3807 $names = array_map('trim', $names); 3808 return $names; 3809 } 3810 3811 }//end findImplementedInterfaceNames() 3812 3813 3814}//end class 3815