annotate vendor/squizlabs/php_codesniffer/CodeSniffer/File.php @ 12:7a779792577d

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