annotate vendor/squizlabs/php_codesniffer/CodeSniffer/File.php @ 2:92f882872392

Trusted hosts, + remove migration modules
author Chris Cannam
date Tue, 05 Dec 2017 09:26:43 +0000
parents 4c8ae668cc8c
children 7a779792577d
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@0 1542 $firstTabSize = ($tabWidth - ($currColumn % $tabWidth) + 1);
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@0 2261 echo '* ignoring curly brace *'.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@0 2272 // If the first non-whitespace/comment token is a
Chris@0 2273 // variable or object operator then this is an opener
Chris@0 2274 // for a string offset and not a scope.
Chris@0 2275 if ($tokens[$x]['code'] === T_VARIABLE
Chris@0 2276 || $tokens[$x]['code'] === T_OBJECT_OPERATOR
Chris@0 2277 ) {
Chris@0 2278 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2279 echo str_repeat("\t", $depth);
Chris@0 2280 echo '* ignoring curly brace *'.PHP_EOL;
Chris@0 2281 }
Chris@0 2282
Chris@0 2283 $ignore++;
Chris@0 2284 }//end if
Chris@0 2285
Chris@0 2286 break;
Chris@0 2287 }//end if
Chris@0 2288 }//end for
Chris@0 2289 }//end if
Chris@0 2290 }//end if
Chris@0 2291
Chris@0 2292 if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
Chris@0 2293 // We found the opening scope token for $currType.
Chris@0 2294 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2295 $type = $tokens[$stackPtr]['type'];
Chris@0 2296 echo str_repeat("\t", $depth);
Chris@0 2297 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
Chris@0 2298 }
Chris@0 2299
Chris@0 2300 $opener = $i;
Chris@0 2301 }
Chris@0 2302 } else if ($tokenType === T_OPEN_PARENTHESIS) {
Chris@0 2303 if (isset($tokens[$i]['parenthesis_owner']) === true) {
Chris@0 2304 $owner = $tokens[$i]['parenthesis_owner'];
Chris@0 2305 if (isset(PHP_CodeSniffer_Tokens::$scopeOpeners[$tokens[$owner]['code']]) === true
Chris@0 2306 && isset($tokens[$i]['parenthesis_closer']) === true
Chris@0 2307 ) {
Chris@0 2308 // If we get into here, then we opened a parenthesis for
Chris@0 2309 // a scope (eg. an if or else if) so we need to update the
Chris@0 2310 // start of the line so that when we check to see
Chris@0 2311 // if the closing parenthesis is more than 3 lines away from
Chris@0 2312 // the statement, we check from the closing parenthesis.
Chris@0 2313 $startLine = $tokens[$tokens[$i]['parenthesis_closer']]['line'];
Chris@0 2314 }
Chris@0 2315 }
Chris@0 2316 } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
Chris@0 2317 // We opened something that we don't have a scope opener for.
Chris@0 2318 // Examples of this are curly brackets for string offsets etc.
Chris@0 2319 // We want to ignore this so that we don't have an invalid scope
Chris@0 2320 // map.
Chris@0 2321 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2322 echo str_repeat("\t", $depth);
Chris@0 2323 echo '* ignoring curly brace *'.PHP_EOL;
Chris@0 2324 }
Chris@0 2325
Chris@0 2326 $ignore++;
Chris@0 2327 } else if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) {
Chris@0 2328 // We found the end token for the opener we were ignoring.
Chris@0 2329 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2330 echo str_repeat("\t", $depth);
Chris@0 2331 echo '* finished ignoring curly brace *'.PHP_EOL;
Chris@0 2332 }
Chris@0 2333
Chris@0 2334 $ignore--;
Chris@0 2335 } else if ($opener === null
Chris@0 2336 && isset($tokenizer->scopeOpeners[$currType]) === true
Chris@0 2337 ) {
Chris@0 2338 // If we still haven't found the opener after 3 lines,
Chris@0 2339 // we're not going to find it, unless we know it requires
Chris@0 2340 // an opener (in which case we better keep looking) or the last
Chris@0 2341 // token was empty (in which case we'll just confirm there is
Chris@0 2342 // more code in this file and not just a big comment).
Chris@0 2343 if ($tokens[$i]['line'] >= ($startLine + 3)
Chris@0 2344 && isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[($i - 1)]['code']]) === false
Chris@0 2345 ) {
Chris@0 2346 if ($tokenizer->scopeOpeners[$currType]['strict'] === true) {
Chris@0 2347 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2348 $type = $tokens[$stackPtr]['type'];
Chris@0 2349 $lines = ($tokens[$i]['line'] - $startLine);
Chris@0 2350 echo str_repeat("\t", $depth);
Chris@0 2351 echo "=> Still looking for $stackPtr:$type scope opener after $lines lines".PHP_EOL;
Chris@0 2352 }
Chris@0 2353 } else {
Chris@0 2354 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2355 $type = $tokens[$stackPtr]['type'];
Chris@0 2356 echo str_repeat("\t", $depth);
Chris@0 2357 echo "=> Couldn't find scope opener for $stackPtr:$type, bailing".PHP_EOL;
Chris@0 2358 }
Chris@0 2359
Chris@0 2360 return $stackPtr;
Chris@0 2361 }
Chris@0 2362 }
Chris@0 2363 } else if ($opener !== null
Chris@0 2364 && $tokenType !== T_BREAK
Chris@0 2365 && isset($tokenizer->endScopeTokens[$tokenType]) === true
Chris@0 2366 ) {
Chris@0 2367 if (isset($tokens[$i]['scope_condition']) === false) {
Chris@0 2368 if ($ignore > 0) {
Chris@0 2369 // We found the end token for the opener we were ignoring.
Chris@0 2370 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2371 echo str_repeat("\t", $depth);
Chris@0 2372 echo '* finished ignoring curly brace *'.PHP_EOL;
Chris@0 2373 }
Chris@0 2374
Chris@0 2375 $ignore--;
Chris@0 2376 } else {
Chris@0 2377 // We found a token that closes the scope but it doesn't
Chris@0 2378 // have a condition, so it belongs to another token and
Chris@0 2379 // our token doesn't have a closer, so pretend this is
Chris@0 2380 // the closer.
Chris@0 2381 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2382 $type = $tokens[$stackPtr]['type'];
Chris@0 2383 echo str_repeat("\t", $depth);
Chris@0 2384 echo "=> Found (unexpected) scope closer for $stackPtr:$type".PHP_EOL;
Chris@0 2385 }
Chris@0 2386
Chris@0 2387 foreach (array($stackPtr, $opener) as $token) {
Chris@0 2388 $tokens[$token]['scope_condition'] = $stackPtr;
Chris@0 2389 $tokens[$token]['scope_opener'] = $opener;
Chris@0 2390 $tokens[$token]['scope_closer'] = $i;
Chris@0 2391 }
Chris@0 2392
Chris@0 2393 return ($i - 1);
Chris@0 2394 }//end if
Chris@0 2395 }//end if
Chris@0 2396 }//end if
Chris@0 2397 }//end for
Chris@0 2398
Chris@0 2399 return $stackPtr;
Chris@0 2400
Chris@0 2401 }//end _recurseScopeMap()
Chris@0 2402
Chris@0 2403
Chris@0 2404 /**
Chris@0 2405 * Constructs the level map.
Chris@0 2406 *
Chris@0 2407 * The level map adds a 'level' index to each token which indicates the
Chris@0 2408 * depth that a token within a set of scope blocks. It also adds a
Chris@0 2409 * 'condition' index which is an array of the scope conditions that opened
Chris@0 2410 * each of the scopes - position 0 being the first scope opener.
Chris@0 2411 *
Chris@0 2412 * @param array $tokens The array of tokens to process.
Chris@0 2413 * @param object $tokenizer The tokenizer being used to process this file.
Chris@0 2414 * @param string $eolChar The EOL character to use for splitting strings.
Chris@0 2415 *
Chris@0 2416 * @return void
Chris@0 2417 */
Chris@0 2418 private static function _createLevelMap(&$tokens, $tokenizer, $eolChar)
Chris@0 2419 {
Chris@0 2420 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2421 echo "\t*** START LEVEL MAP ***".PHP_EOL;
Chris@0 2422 }
Chris@0 2423
Chris@0 2424 $numTokens = count($tokens);
Chris@0 2425 $level = 0;
Chris@0 2426 $conditions = array();
Chris@0 2427 $lastOpener = null;
Chris@0 2428 $openers = array();
Chris@0 2429
Chris@0 2430 for ($i = 0; $i < $numTokens; $i++) {
Chris@0 2431 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2432 $type = $tokens[$i]['type'];
Chris@0 2433 $line = $tokens[$i]['line'];
Chris@0 2434 $len = $tokens[$i]['length'];
Chris@0 2435 $col = $tokens[$i]['column'];
Chris@0 2436
Chris@0 2437 $content = PHP_CodeSniffer::prepareForOutput($tokens[$i]['content']);
Chris@0 2438
Chris@0 2439 echo str_repeat("\t", ($level + 1));
Chris@0 2440 echo "Process token $i on line $line [col:$col;len:$len;lvl:$level;";
Chris@0 2441 if (empty($conditions) !== true) {
Chris@0 2442 $condString = 'conds;';
Chris@0 2443 foreach ($conditions as $condition) {
Chris@0 2444 $condString .= token_name($condition).',';
Chris@0 2445 }
Chris@0 2446
Chris@0 2447 echo rtrim($condString, ',').';';
Chris@0 2448 }
Chris@0 2449
Chris@0 2450 echo "]: $type => $content".PHP_EOL;
Chris@0 2451 }//end if
Chris@0 2452
Chris@0 2453 $tokens[$i]['level'] = $level;
Chris@0 2454 $tokens[$i]['conditions'] = $conditions;
Chris@0 2455
Chris@0 2456 if (isset($tokens[$i]['scope_condition']) === true) {
Chris@0 2457 // Check to see if this token opened the scope.
Chris@0 2458 if ($tokens[$i]['scope_opener'] === $i) {
Chris@0 2459 $stackPtr = $tokens[$i]['scope_condition'];
Chris@0 2460 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2461 $type = $tokens[$stackPtr]['type'];
Chris@0 2462 echo str_repeat("\t", ($level + 1));
Chris@0 2463 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
Chris@0 2464 }
Chris@0 2465
Chris@0 2466 $stackPtr = $tokens[$i]['scope_condition'];
Chris@0 2467
Chris@0 2468 // If we find a scope opener that has a shared closer,
Chris@0 2469 // then we need to go back over the condition map that we
Chris@0 2470 // just created and fix ourselves as we just added some
Chris@0 2471 // conditions where there was none. This happens for T_CASE
Chris@0 2472 // statements that are using the same break statement.
Chris@0 2473 if ($lastOpener !== null && $tokens[$lastOpener]['scope_closer'] === $tokens[$i]['scope_closer']) {
Chris@0 2474 // This opener shares its closer with the previous opener,
Chris@0 2475 // but we still need to check if the two openers share their
Chris@0 2476 // closer with each other directly (like CASE and DEFAULT)
Chris@0 2477 // or if they are just sharing because one doesn't have a
Chris@0 2478 // closer (like CASE with no BREAK using a SWITCHes closer).
Chris@0 2479 $thisType = $tokens[$tokens[$i]['scope_condition']]['code'];
Chris@0 2480 $opener = $tokens[$lastOpener]['scope_condition'];
Chris@0 2481
Chris@0 2482 $isShared = isset($tokenizer->scopeOpeners[$thisType]['with'][$tokens[$opener]['code']]);
Chris@0 2483
Chris@0 2484 reset($tokenizer->scopeOpeners[$thisType]['end']);
Chris@0 2485 reset($tokenizer->scopeOpeners[$tokens[$opener]['code']]['end']);
Chris@0 2486 $sameEnd = (current($tokenizer->scopeOpeners[$thisType]['end']) === current($tokenizer->scopeOpeners[$tokens[$opener]['code']]['end']));
Chris@0 2487
Chris@0 2488 if ($isShared === true && $sameEnd === true) {
Chris@0 2489 $badToken = $opener;
Chris@0 2490 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2491 $type = $tokens[$badToken]['type'];
Chris@0 2492 echo str_repeat("\t", ($level + 1));
Chris@0 2493 echo "* shared closer, cleaning up $badToken:$type *".PHP_EOL;
Chris@0 2494 }
Chris@0 2495
Chris@0 2496 for ($x = $tokens[$i]['scope_condition']; $x <= $i; $x++) {
Chris@0 2497 $oldConditions = $tokens[$x]['conditions'];
Chris@0 2498 $oldLevel = $tokens[$x]['level'];
Chris@0 2499 $tokens[$x]['level']--;
Chris@0 2500 unset($tokens[$x]['conditions'][$badToken]);
Chris@0 2501 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2502 $type = $tokens[$x]['type'];
Chris@0 2503 $oldConds = '';
Chris@0 2504 foreach ($oldConditions as $condition) {
Chris@0 2505 $oldConds .= token_name($condition).',';
Chris@0 2506 }
Chris@0 2507
Chris@0 2508 $oldConds = rtrim($oldConds, ',');
Chris@0 2509
Chris@0 2510 $newConds = '';
Chris@0 2511 foreach ($tokens[$x]['conditions'] as $condition) {
Chris@0 2512 $newConds .= token_name($condition).',';
Chris@0 2513 }
Chris@0 2514
Chris@0 2515 $newConds = rtrim($newConds, ',');
Chris@0 2516
Chris@0 2517 $newLevel = $tokens[$x]['level'];
Chris@0 2518 echo str_repeat("\t", ($level + 1));
Chris@0 2519 echo "* cleaned $x:$type *".PHP_EOL;
Chris@0 2520 echo str_repeat("\t", ($level + 2));
Chris@0 2521 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
Chris@0 2522 echo str_repeat("\t", ($level + 2));
Chris@0 2523 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
Chris@0 2524 }//end if
Chris@0 2525 }//end for
Chris@0 2526
Chris@0 2527 unset($conditions[$badToken]);
Chris@0 2528 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2529 $type = $tokens[$badToken]['type'];
Chris@0 2530 echo str_repeat("\t", ($level + 1));
Chris@0 2531 echo "* token $badToken:$type removed from conditions array *".PHP_EOL;
Chris@0 2532 }
Chris@0 2533
Chris@0 2534 unset($openers[$lastOpener]);
Chris@0 2535
Chris@0 2536 $level--;
Chris@0 2537 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2538 echo str_repeat("\t", ($level + 2));
Chris@0 2539 echo '* level decreased *'.PHP_EOL;
Chris@0 2540 }
Chris@0 2541 }//end if
Chris@0 2542 }//end if
Chris@0 2543
Chris@0 2544 $level++;
Chris@0 2545 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2546 echo str_repeat("\t", ($level + 1));
Chris@0 2547 echo '* level increased *'.PHP_EOL;
Chris@0 2548 }
Chris@0 2549
Chris@0 2550 $conditions[$stackPtr] = $tokens[$stackPtr]['code'];
Chris@0 2551 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2552 $type = $tokens[$stackPtr]['type'];
Chris@0 2553 echo str_repeat("\t", ($level + 1));
Chris@0 2554 echo "* token $stackPtr:$type added to conditions array *".PHP_EOL;
Chris@0 2555 }
Chris@0 2556
Chris@0 2557 $lastOpener = $tokens[$i]['scope_opener'];
Chris@0 2558 if ($lastOpener !== null) {
Chris@0 2559 $openers[$lastOpener] = $lastOpener;
Chris@0 2560 }
Chris@0 2561 } else if ($lastOpener !== null && $tokens[$lastOpener]['scope_closer'] === $i) {
Chris@0 2562 foreach (array_reverse($openers) as $opener) {
Chris@0 2563 if ($tokens[$opener]['scope_closer'] === $i) {
Chris@0 2564 $oldOpener = array_pop($openers);
Chris@0 2565 if (empty($openers) === false) {
Chris@0 2566 $lastOpener = array_pop($openers);
Chris@0 2567 $openers[$lastOpener] = $lastOpener;
Chris@0 2568 } else {
Chris@0 2569 $lastOpener = null;
Chris@0 2570 }
Chris@0 2571
Chris@0 2572 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2573 $type = $tokens[$oldOpener]['type'];
Chris@0 2574 echo str_repeat("\t", ($level + 1));
Chris@0 2575 echo "=> Found scope closer for $oldOpener:$type".PHP_EOL;
Chris@0 2576 }
Chris@0 2577
Chris@0 2578 $oldCondition = array_pop($conditions);
Chris@0 2579 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2580 echo str_repeat("\t", ($level + 1));
Chris@0 2581 echo '* token '.token_name($oldCondition).' removed from conditions array *'.PHP_EOL;
Chris@0 2582 }
Chris@0 2583
Chris@0 2584 // Make sure this closer actually belongs to us.
Chris@0 2585 // Either the condition also has to think this is the
Chris@0 2586 // closer, or it has to allow sharing with us.
Chris@0 2587 $condition = $tokens[$tokens[$i]['scope_condition']]['code'];
Chris@0 2588 if ($condition !== $oldCondition) {
Chris@0 2589 if (isset($tokenizer->scopeOpeners[$oldCondition]['with'][$condition]) === false) {
Chris@0 2590 $badToken = $tokens[$oldOpener]['scope_condition'];
Chris@0 2591
Chris@0 2592 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2593 $type = token_name($oldCondition);
Chris@0 2594 echo str_repeat("\t", ($level + 1));
Chris@0 2595 echo "* scope closer was bad, cleaning up $badToken:$type *".PHP_EOL;
Chris@0 2596 }
Chris@0 2597
Chris@0 2598 for ($x = ($oldOpener + 1); $x <= $i; $x++) {
Chris@0 2599 $oldConditions = $tokens[$x]['conditions'];
Chris@0 2600 $oldLevel = $tokens[$x]['level'];
Chris@0 2601 $tokens[$x]['level']--;
Chris@0 2602 unset($tokens[$x]['conditions'][$badToken]);
Chris@0 2603 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2604 $type = $tokens[$x]['type'];
Chris@0 2605 $oldConds = '';
Chris@0 2606 foreach ($oldConditions as $condition) {
Chris@0 2607 $oldConds .= token_name($condition).',';
Chris@0 2608 }
Chris@0 2609
Chris@0 2610 $oldConds = rtrim($oldConds, ',');
Chris@0 2611
Chris@0 2612 $newConds = '';
Chris@0 2613 foreach ($tokens[$x]['conditions'] as $condition) {
Chris@0 2614 $newConds .= token_name($condition).',';
Chris@0 2615 }
Chris@0 2616
Chris@0 2617 $newConds = rtrim($newConds, ',');
Chris@0 2618
Chris@0 2619 $newLevel = $tokens[$x]['level'];
Chris@0 2620 echo str_repeat("\t", ($level + 1));
Chris@0 2621 echo "* cleaned $x:$type *".PHP_EOL;
Chris@0 2622 echo str_repeat("\t", ($level + 2));
Chris@0 2623 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
Chris@0 2624 echo str_repeat("\t", ($level + 2));
Chris@0 2625 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
Chris@0 2626 }//end if
Chris@0 2627 }//end for
Chris@0 2628 }//end if
Chris@0 2629 }//end if
Chris@0 2630
Chris@0 2631 $level--;
Chris@0 2632 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2633 echo str_repeat("\t", ($level + 2));
Chris@0 2634 echo '* level decreased *'.PHP_EOL;
Chris@0 2635 }
Chris@0 2636
Chris@0 2637 $tokens[$i]['level'] = $level;
Chris@0 2638 $tokens[$i]['conditions'] = $conditions;
Chris@0 2639 }//end if
Chris@0 2640 }//end foreach
Chris@0 2641 }//end if
Chris@0 2642 }//end if
Chris@0 2643 }//end for
Chris@0 2644
Chris@0 2645 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 2646 echo "\t*** END LEVEL MAP ***".PHP_EOL;
Chris@0 2647 }
Chris@0 2648
Chris@0 2649 }//end _createLevelMap()
Chris@0 2650
Chris@0 2651
Chris@0 2652 /**
Chris@0 2653 * Returns the declaration names for classes, interfaces, and functions.
Chris@0 2654 *
Chris@0 2655 * @param int $stackPtr The position of the declaration token which
Chris@0 2656 * declared the class, interface or function.
Chris@0 2657 *
Chris@0 2658 * @return string|null The name of the class, interface or function.
Chris@0 2659 * or NULL if the function or class is anonymous.
Chris@0 2660 * @throws PHP_CodeSniffer_Exception If the specified token is not of type
Chris@0 2661 * T_FUNCTION, T_CLASS, T_ANON_CLASS,
Chris@0 2662 * or T_INTERFACE.
Chris@0 2663 */
Chris@0 2664 public function getDeclarationName($stackPtr)
Chris@0 2665 {
Chris@0 2666 $tokenCode = $this->_tokens[$stackPtr]['code'];
Chris@0 2667
Chris@0 2668 if ($tokenCode === T_ANON_CLASS) {
Chris@0 2669 return null;
Chris@0 2670 }
Chris@0 2671
Chris@0 2672 if ($tokenCode === T_FUNCTION
Chris@0 2673 && $this->isAnonymousFunction($stackPtr) === true
Chris@0 2674 ) {
Chris@0 2675 return null;
Chris@0 2676 }
Chris@0 2677
Chris@0 2678 if ($tokenCode !== T_FUNCTION
Chris@0 2679 && $tokenCode !== T_CLASS
Chris@0 2680 && $tokenCode !== T_INTERFACE
Chris@0 2681 && $tokenCode !== T_TRAIT
Chris@0 2682 ) {
Chris@0 2683 throw new PHP_CodeSniffer_Exception('Token type "'.$this->_tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
Chris@0 2684 }
Chris@0 2685
Chris@0 2686 $content = null;
Chris@0 2687 for ($i = $stackPtr; $i < $this->numTokens; $i++) {
Chris@0 2688 if ($this->_tokens[$i]['code'] === T_STRING) {
Chris@0 2689 $content = $this->_tokens[$i]['content'];
Chris@0 2690 break;
Chris@0 2691 }
Chris@0 2692 }
Chris@0 2693
Chris@0 2694 return $content;
Chris@0 2695
Chris@0 2696 }//end getDeclarationName()
Chris@0 2697
Chris@0 2698
Chris@0 2699 /**
Chris@0 2700 * Check if the token at the specified position is a anonymous function.
Chris@0 2701 *
Chris@0 2702 * @param int $stackPtr The position of the declaration token which
Chris@0 2703 * declared the class, interface or function.
Chris@0 2704 *
Chris@0 2705 * @return boolean
Chris@0 2706 * @throws PHP_CodeSniffer_Exception If the specified token is not of type
Chris@0 2707 * T_FUNCTION
Chris@0 2708 */
Chris@0 2709 public function isAnonymousFunction($stackPtr)
Chris@0 2710 {
Chris@0 2711 $tokenCode = $this->_tokens[$stackPtr]['code'];
Chris@0 2712 if ($tokenCode !== T_FUNCTION) {
Chris@0 2713 throw new PHP_CodeSniffer_Exception('Token type is not T_FUNCTION');
Chris@0 2714 }
Chris@0 2715
Chris@0 2716 if (isset($this->_tokens[$stackPtr]['parenthesis_opener']) === false) {
Chris@0 2717 // Something is not right with this function.
Chris@0 2718 return false;
Chris@0 2719 }
Chris@0 2720
Chris@0 2721 $name = false;
Chris@0 2722 for ($i = ($stackPtr + 1); $i < $this->numTokens; $i++) {
Chris@0 2723 if ($this->_tokens[$i]['code'] === T_STRING) {
Chris@0 2724 $name = $i;
Chris@0 2725 break;
Chris@0 2726 }
Chris@0 2727 }
Chris@0 2728
Chris@0 2729 if ($name === false) {
Chris@0 2730 // No name found.
Chris@0 2731 return true;
Chris@0 2732 }
Chris@0 2733
Chris@0 2734 $open = $this->_tokens[$stackPtr]['parenthesis_opener'];
Chris@0 2735 if ($name > $open) {
Chris@0 2736 return true;
Chris@0 2737 }
Chris@0 2738
Chris@0 2739 return false;
Chris@0 2740
Chris@0 2741 }//end isAnonymousFunction()
Chris@0 2742
Chris@0 2743
Chris@0 2744 /**
Chris@0 2745 * Returns the method parameters for the specified function token.
Chris@0 2746 *
Chris@0 2747 * Each parameter is in the following format:
Chris@0 2748 *
Chris@0 2749 * <code>
Chris@0 2750 * 0 => array(
Chris@0 2751 * 'token' => int, // The position of the var in the token stack.
Chris@0 2752 * 'name' => '$var', // The variable name.
Chris@0 2753 * 'content' => string, // The full content of the variable definition.
Chris@0 2754 * 'pass_by_reference' => boolean, // Is the variable passed by reference?
Chris@0 2755 * 'type_hint' => string, // The type hint for the variable.
Chris@0 2756 * 'nullable_type' => boolean, // Is the variable using a nullable type?
Chris@0 2757 * )
Chris@0 2758 * </code>
Chris@0 2759 *
Chris@0 2760 * Parameters with default values have an additional array index of
Chris@0 2761 * 'default' with the value of the default as a string.
Chris@0 2762 *
Chris@0 2763 * @param int $stackPtr The position in the stack of the function token
Chris@0 2764 * to acquire the parameters for.
Chris@0 2765 *
Chris@0 2766 * @return array
Chris@0 2767 * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of
Chris@0 2768 * type T_FUNCTION or T_CLOSURE.
Chris@0 2769 */
Chris@0 2770 public function getMethodParameters($stackPtr)
Chris@0 2771 {
Chris@0 2772 if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION
Chris@0 2773 && $this->_tokens[$stackPtr]['code'] !== T_CLOSURE
Chris@0 2774 ) {
Chris@0 2775 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
Chris@0 2776 }
Chris@0 2777
Chris@0 2778 $opener = $this->_tokens[$stackPtr]['parenthesis_opener'];
Chris@0 2779 $closer = $this->_tokens[$stackPtr]['parenthesis_closer'];
Chris@0 2780
Chris@0 2781 $vars = array();
Chris@0 2782 $currVar = null;
Chris@0 2783 $paramStart = ($opener + 1);
Chris@0 2784 $defaultStart = null;
Chris@0 2785 $paramCount = 0;
Chris@0 2786 $passByReference = false;
Chris@0 2787 $variableLength = false;
Chris@0 2788 $typeHint = '';
Chris@0 2789 $nullableType = false;
Chris@0 2790
Chris@0 2791 for ($i = $paramStart; $i <= $closer; $i++) {
Chris@0 2792 // Check to see if this token has a parenthesis or bracket opener. If it does
Chris@0 2793 // it's likely to be an array which might have arguments in it. This
Chris@0 2794 // could cause problems in our parsing below, so lets just skip to the
Chris@0 2795 // end of it.
Chris@0 2796 if (isset($this->_tokens[$i]['parenthesis_opener']) === true) {
Chris@0 2797 // Don't do this if it's the close parenthesis for the method.
Chris@0 2798 if ($i !== $this->_tokens[$i]['parenthesis_closer']) {
Chris@0 2799 $i = ($this->_tokens[$i]['parenthesis_closer'] + 1);
Chris@0 2800 }
Chris@0 2801 }
Chris@0 2802
Chris@0 2803 if (isset($this->_tokens[$i]['bracket_opener']) === true) {
Chris@0 2804 // Don't do this if it's the close parenthesis for the method.
Chris@0 2805 if ($i !== $this->_tokens[$i]['bracket_closer']) {
Chris@0 2806 $i = ($this->_tokens[$i]['bracket_closer'] + 1);
Chris@0 2807 }
Chris@0 2808 }
Chris@0 2809
Chris@0 2810 switch ($this->_tokens[$i]['code']) {
Chris@0 2811 case T_BITWISE_AND:
Chris@0 2812 $passByReference = true;
Chris@0 2813 break;
Chris@0 2814 case T_VARIABLE:
Chris@0 2815 $currVar = $i;
Chris@0 2816 break;
Chris@0 2817 case T_ELLIPSIS:
Chris@0 2818 $variableLength = true;
Chris@0 2819 break;
Chris@0 2820 case T_ARRAY_HINT:
Chris@0 2821 case T_CALLABLE:
Chris@0 2822 $typeHint .= $this->_tokens[$i]['content'];
Chris@0 2823 break;
Chris@0 2824 case T_SELF:
Chris@0 2825 case T_PARENT:
Chris@0 2826 case T_STATIC:
Chris@0 2827 // Self is valid, the others invalid, but were probably intended as type hints.
Chris@0 2828 if (isset($defaultStart) === false) {
Chris@0 2829 $typeHint .= $this->_tokens[$i]['content'];
Chris@0 2830 }
Chris@0 2831 break;
Chris@0 2832 case T_STRING:
Chris@0 2833 // This is a string, so it may be a type hint, but it could
Chris@0 2834 // also be a constant used as a default value.
Chris@0 2835 $prevComma = false;
Chris@0 2836 for ($t = $i; $t >= $opener; $t--) {
Chris@0 2837 if ($this->_tokens[$t]['code'] === T_COMMA) {
Chris@0 2838 $prevComma = $t;
Chris@0 2839 break;
Chris@0 2840 }
Chris@0 2841 }
Chris@0 2842
Chris@0 2843 if ($prevComma !== false) {
Chris@0 2844 $nextEquals = false;
Chris@0 2845 for ($t = $prevComma; $t < $i; $t++) {
Chris@0 2846 if ($this->_tokens[$t]['code'] === T_EQUAL) {
Chris@0 2847 $nextEquals = $t;
Chris@0 2848 break;
Chris@0 2849 }
Chris@0 2850 }
Chris@0 2851
Chris@0 2852 if ($nextEquals !== false) {
Chris@0 2853 break;
Chris@0 2854 }
Chris@0 2855 }
Chris@0 2856
Chris@0 2857 if ($defaultStart === null) {
Chris@0 2858 $typeHint .= $this->_tokens[$i]['content'];
Chris@0 2859 }
Chris@0 2860 break;
Chris@0 2861 case T_NS_SEPARATOR:
Chris@0 2862 // Part of a type hint or default value.
Chris@0 2863 if ($defaultStart === null) {
Chris@0 2864 $typeHint .= $this->_tokens[$i]['content'];
Chris@0 2865 }
Chris@0 2866 break;
Chris@0 2867 case T_NULLABLE:
Chris@0 2868 if ($defaultStart === null) {
Chris@0 2869 $nullableType = true;
Chris@0 2870 $typeHint .= $this->_tokens[$i]['content'];
Chris@0 2871 }
Chris@0 2872 break;
Chris@0 2873 case T_CLOSE_PARENTHESIS:
Chris@0 2874 case T_COMMA:
Chris@0 2875 // If it's null, then there must be no parameters for this
Chris@0 2876 // method.
Chris@0 2877 if ($currVar === null) {
Chris@0 2878 continue;
Chris@0 2879 }
Chris@0 2880
Chris@0 2881 $vars[$paramCount] = array();
Chris@0 2882 $vars[$paramCount]['token'] = $currVar;
Chris@0 2883 $vars[$paramCount]['name'] = $this->_tokens[$currVar]['content'];
Chris@0 2884 $vars[$paramCount]['content'] = trim($this->getTokensAsString($paramStart, ($i - $paramStart)));
Chris@0 2885
Chris@0 2886 if ($defaultStart !== null) {
Chris@0 2887 $vars[$paramCount]['default'] = trim($this->getTokensAsString($defaultStart, ($i - $defaultStart)));
Chris@0 2888 }
Chris@0 2889
Chris@0 2890 $vars[$paramCount]['pass_by_reference'] = $passByReference;
Chris@0 2891 $vars[$paramCount]['variable_length'] = $variableLength;
Chris@0 2892 $vars[$paramCount]['type_hint'] = $typeHint;
Chris@0 2893 $vars[$paramCount]['nullable_type'] = $nullableType;
Chris@0 2894
Chris@0 2895 // Reset the vars, as we are about to process the next parameter.
Chris@0 2896 $defaultStart = null;
Chris@0 2897 $paramStart = ($i + 1);
Chris@0 2898 $passByReference = false;
Chris@0 2899 $variableLength = false;
Chris@0 2900 $typeHint = '';
Chris@0 2901 $nullableType = false;
Chris@0 2902
Chris@0 2903 $paramCount++;
Chris@0 2904 break;
Chris@0 2905 case T_EQUAL:
Chris@0 2906 $defaultStart = ($i + 1);
Chris@0 2907 break;
Chris@0 2908 }//end switch
Chris@0 2909 }//end for
Chris@0 2910
Chris@0 2911 return $vars;
Chris@0 2912
Chris@0 2913 }//end getMethodParameters()
Chris@0 2914
Chris@0 2915
Chris@0 2916 /**
Chris@0 2917 * Returns the visibility and implementation properties of a method.
Chris@0 2918 *
Chris@0 2919 * The format of the array is:
Chris@0 2920 * <code>
Chris@0 2921 * array(
Chris@0 2922 * 'scope' => 'public', // public private or protected
Chris@0 2923 * 'scope_specified' => true, // true is scope keyword was found.
Chris@0 2924 * 'is_abstract' => false, // true if the abstract keyword was found.
Chris@0 2925 * 'is_final' => false, // true if the final keyword was found.
Chris@0 2926 * 'is_static' => false, // true if the static keyword was found.
Chris@0 2927 * 'is_closure' => false, // true if no name is found.
Chris@0 2928 * );
Chris@0 2929 * </code>
Chris@0 2930 *
Chris@0 2931 * @param int $stackPtr The position in the stack of the T_FUNCTION token to
Chris@0 2932 * acquire the properties for.
Chris@0 2933 *
Chris@0 2934 * @return array
Chris@0 2935 * @throws PHP_CodeSniffer_Exception If the specified position is not a
Chris@0 2936 * T_FUNCTION token.
Chris@0 2937 */
Chris@0 2938 public function getMethodProperties($stackPtr)
Chris@0 2939 {
Chris@0 2940 if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) {
Chris@0 2941 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION');
Chris@0 2942 }
Chris@0 2943
Chris@0 2944 $valid = array(
Chris@0 2945 T_PUBLIC => T_PUBLIC,
Chris@0 2946 T_PRIVATE => T_PRIVATE,
Chris@0 2947 T_PROTECTED => T_PROTECTED,
Chris@0 2948 T_STATIC => T_STATIC,
Chris@0 2949 T_FINAL => T_FINAL,
Chris@0 2950 T_ABSTRACT => T_ABSTRACT,
Chris@0 2951 T_WHITESPACE => T_WHITESPACE,
Chris@0 2952 T_COMMENT => T_COMMENT,
Chris@0 2953 T_DOC_COMMENT => T_DOC_COMMENT,
Chris@0 2954 );
Chris@0 2955
Chris@0 2956 $scope = 'public';
Chris@0 2957 $scopeSpecified = false;
Chris@0 2958 $isAbstract = false;
Chris@0 2959 $isFinal = false;
Chris@0 2960 $isStatic = false;
Chris@0 2961 $isClosure = $this->isAnonymousFunction($stackPtr);
Chris@0 2962
Chris@0 2963 for ($i = ($stackPtr - 1); $i > 0; $i--) {
Chris@0 2964 if (isset($valid[$this->_tokens[$i]['code']]) === false) {
Chris@0 2965 break;
Chris@0 2966 }
Chris@0 2967
Chris@0 2968 switch ($this->_tokens[$i]['code']) {
Chris@0 2969 case T_PUBLIC:
Chris@0 2970 $scope = 'public';
Chris@0 2971 $scopeSpecified = true;
Chris@0 2972 break;
Chris@0 2973 case T_PRIVATE:
Chris@0 2974 $scope = 'private';
Chris@0 2975 $scopeSpecified = true;
Chris@0 2976 break;
Chris@0 2977 case T_PROTECTED:
Chris@0 2978 $scope = 'protected';
Chris@0 2979 $scopeSpecified = true;
Chris@0 2980 break;
Chris@0 2981 case T_ABSTRACT:
Chris@0 2982 $isAbstract = true;
Chris@0 2983 break;
Chris@0 2984 case T_FINAL:
Chris@0 2985 $isFinal = true;
Chris@0 2986 break;
Chris@0 2987 case T_STATIC:
Chris@0 2988 $isStatic = true;
Chris@0 2989 break;
Chris@0 2990 }//end switch
Chris@0 2991 }//end for
Chris@0 2992
Chris@0 2993 return array(
Chris@0 2994 'scope' => $scope,
Chris@0 2995 'scope_specified' => $scopeSpecified,
Chris@0 2996 'is_abstract' => $isAbstract,
Chris@0 2997 'is_final' => $isFinal,
Chris@0 2998 'is_static' => $isStatic,
Chris@0 2999 'is_closure' => $isClosure,
Chris@0 3000 );
Chris@0 3001
Chris@0 3002 }//end getMethodProperties()
Chris@0 3003
Chris@0 3004
Chris@0 3005 /**
Chris@0 3006 * Returns the visibility and implementation properties of the class member
Chris@0 3007 * variable found at the specified position in the stack.
Chris@0 3008 *
Chris@0 3009 * The format of the array is:
Chris@0 3010 *
Chris@0 3011 * <code>
Chris@0 3012 * array(
Chris@0 3013 * 'scope' => 'public', // public private or protected
Chris@0 3014 * 'is_static' => false, // true if the static keyword was found.
Chris@0 3015 * );
Chris@0 3016 * </code>
Chris@0 3017 *
Chris@0 3018 * @param int $stackPtr The position in the stack of the T_VARIABLE token to
Chris@0 3019 * acquire the properties for.
Chris@0 3020 *
Chris@0 3021 * @return array
Chris@0 3022 * @throws PHP_CodeSniffer_Exception If the specified position is not a
Chris@0 3023 * T_VARIABLE token, or if the position is not
Chris@0 3024 * a class member variable.
Chris@0 3025 */
Chris@0 3026 public function getMemberProperties($stackPtr)
Chris@0 3027 {
Chris@0 3028 if ($this->_tokens[$stackPtr]['code'] !== T_VARIABLE) {
Chris@0 3029 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_VARIABLE');
Chris@0 3030 }
Chris@0 3031
Chris@0 3032 $conditions = array_keys($this->_tokens[$stackPtr]['conditions']);
Chris@0 3033 $ptr = array_pop($conditions);
Chris@0 3034 if (isset($this->_tokens[$ptr]) === false
Chris@0 3035 || ($this->_tokens[$ptr]['code'] !== T_CLASS
Chris@0 3036 && $this->_tokens[$ptr]['code'] !== T_ANON_CLASS
Chris@0 3037 && $this->_tokens[$ptr]['code'] !== T_TRAIT)
Chris@0 3038 ) {
Chris@0 3039 if (isset($this->_tokens[$ptr]) === true
Chris@0 3040 && $this->_tokens[$ptr]['code'] === T_INTERFACE
Chris@0 3041 ) {
Chris@0 3042 // T_VARIABLEs in interfaces can actually be method arguments
Chris@0 3043 // but they wont be seen as being inside the method because there
Chris@0 3044 // are no scope openers and closers for abstract methods. If it is in
Chris@0 3045 // parentheses, we can be pretty sure it is a method argument.
Chris@0 3046 if (isset($this->_tokens[$stackPtr]['nested_parenthesis']) === false
Chris@0 3047 || empty($this->_tokens[$stackPtr]['nested_parenthesis']) === true
Chris@0 3048 ) {
Chris@0 3049 $error = 'Possible parse error: interfaces may not include member vars';
Chris@0 3050 $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
Chris@0 3051 return array();
Chris@0 3052 }
Chris@0 3053 } else {
Chris@0 3054 throw new PHP_CodeSniffer_Exception('$stackPtr is not a class member var');
Chris@0 3055 }
Chris@0 3056 }
Chris@0 3057
Chris@0 3058 $valid = array(
Chris@0 3059 T_PUBLIC => T_PUBLIC,
Chris@0 3060 T_PRIVATE => T_PRIVATE,
Chris@0 3061 T_PROTECTED => T_PROTECTED,
Chris@0 3062 T_STATIC => T_STATIC,
Chris@0 3063 T_WHITESPACE => T_WHITESPACE,
Chris@0 3064 T_COMMENT => T_COMMENT,
Chris@0 3065 T_DOC_COMMENT => T_DOC_COMMENT,
Chris@0 3066 T_VARIABLE => T_VARIABLE,
Chris@0 3067 T_COMMA => T_COMMA,
Chris@0 3068 );
Chris@0 3069
Chris@0 3070 $scope = 'public';
Chris@0 3071 $scopeSpecified = false;
Chris@0 3072 $isStatic = false;
Chris@0 3073
Chris@0 3074 for ($i = ($stackPtr - 1); $i > 0; $i--) {
Chris@0 3075 if (isset($valid[$this->_tokens[$i]['code']]) === false) {
Chris@0 3076 break;
Chris@0 3077 }
Chris@0 3078
Chris@0 3079 switch ($this->_tokens[$i]['code']) {
Chris@0 3080 case T_PUBLIC:
Chris@0 3081 $scope = 'public';
Chris@0 3082 $scopeSpecified = true;
Chris@0 3083 break;
Chris@0 3084 case T_PRIVATE:
Chris@0 3085 $scope = 'private';
Chris@0 3086 $scopeSpecified = true;
Chris@0 3087 break;
Chris@0 3088 case T_PROTECTED:
Chris@0 3089 $scope = 'protected';
Chris@0 3090 $scopeSpecified = true;
Chris@0 3091 break;
Chris@0 3092 case T_STATIC:
Chris@0 3093 $isStatic = true;
Chris@0 3094 break;
Chris@0 3095 }
Chris@0 3096 }//end for
Chris@0 3097
Chris@0 3098 return array(
Chris@0 3099 'scope' => $scope,
Chris@0 3100 'scope_specified' => $scopeSpecified,
Chris@0 3101 'is_static' => $isStatic,
Chris@0 3102 );
Chris@0 3103
Chris@0 3104 }//end getMemberProperties()
Chris@0 3105
Chris@0 3106
Chris@0 3107 /**
Chris@0 3108 * Returns the visibility and implementation properties of a class.
Chris@0 3109 *
Chris@0 3110 * The format of the array is:
Chris@0 3111 * <code>
Chris@0 3112 * array(
Chris@0 3113 * 'is_abstract' => false, // true if the abstract keyword was found.
Chris@0 3114 * 'is_final' => false, // true if the final keyword was found.
Chris@0 3115 * );
Chris@0 3116 * </code>
Chris@0 3117 *
Chris@0 3118 * @param int $stackPtr The position in the stack of the T_CLASS token to
Chris@0 3119 * acquire the properties for.
Chris@0 3120 *
Chris@0 3121 * @return array
Chris@0 3122 * @throws PHP_CodeSniffer_Exception If the specified position is not a
Chris@0 3123 * T_CLASS token.
Chris@0 3124 */
Chris@0 3125 public function getClassProperties($stackPtr)
Chris@0 3126 {
Chris@0 3127 if ($this->_tokens[$stackPtr]['code'] !== T_CLASS) {
Chris@0 3128 throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_CLASS');
Chris@0 3129 }
Chris@0 3130
Chris@0 3131 $valid = array(
Chris@0 3132 T_FINAL => T_FINAL,
Chris@0 3133 T_ABSTRACT => T_ABSTRACT,
Chris@0 3134 T_WHITESPACE => T_WHITESPACE,
Chris@0 3135 T_COMMENT => T_COMMENT,
Chris@0 3136 T_DOC_COMMENT => T_DOC_COMMENT,
Chris@0 3137 );
Chris@0 3138
Chris@0 3139 $isAbstract = false;
Chris@0 3140 $isFinal = false;
Chris@0 3141
Chris@0 3142 for ($i = ($stackPtr - 1); $i > 0; $i--) {
Chris@0 3143 if (isset($valid[$this->_tokens[$i]['code']]) === false) {
Chris@0 3144 break;
Chris@0 3145 }
Chris@0 3146
Chris@0 3147 switch ($this->_tokens[$i]['code']) {
Chris@0 3148 case T_ABSTRACT:
Chris@0 3149 $isAbstract = true;
Chris@0 3150 break;
Chris@0 3151
Chris@0 3152 case T_FINAL:
Chris@0 3153 $isFinal = true;
Chris@0 3154 break;
Chris@0 3155 }
Chris@0 3156 }//end for
Chris@0 3157
Chris@0 3158 return array(
Chris@0 3159 'is_abstract' => $isAbstract,
Chris@0 3160 'is_final' => $isFinal,
Chris@0 3161 );
Chris@0 3162
Chris@0 3163 }//end getClassProperties()
Chris@0 3164
Chris@0 3165
Chris@0 3166 /**
Chris@0 3167 * Determine if the passed token is a reference operator.
Chris@0 3168 *
Chris@0 3169 * Returns true if the specified token position represents a reference.
Chris@0 3170 * Returns false if the token represents a bitwise operator.
Chris@0 3171 *
Chris@0 3172 * @param int $stackPtr The position of the T_BITWISE_AND token.
Chris@0 3173 *
Chris@0 3174 * @return boolean
Chris@0 3175 */
Chris@0 3176 public function isReference($stackPtr)
Chris@0 3177 {
Chris@0 3178 if ($this->_tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
Chris@0 3179 return false;
Chris@0 3180 }
Chris@0 3181
Chris@0 3182 $tokenBefore = $this->findPrevious(
Chris@0 3183 PHP_CodeSniffer_Tokens::$emptyTokens,
Chris@0 3184 ($stackPtr - 1),
Chris@0 3185 null,
Chris@0 3186 true
Chris@0 3187 );
Chris@0 3188
Chris@0 3189 if ($this->_tokens[$tokenBefore]['code'] === T_FUNCTION) {
Chris@0 3190 // Function returns a reference.
Chris@0 3191 return true;
Chris@0 3192 }
Chris@0 3193
Chris@0 3194 if ($this->_tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
Chris@0 3195 // Inside a foreach loop, this is a reference.
Chris@0 3196 return true;
Chris@0 3197 }
Chris@0 3198
Chris@0 3199 if ($this->_tokens[$tokenBefore]['code'] === T_AS) {
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_OPEN_SHORT_ARRAY) {
Chris@0 3205 // Inside an array declaration, this is a reference.
Chris@0 3206 return true;
Chris@0 3207 }
Chris@0 3208
Chris@0 3209 if (isset(PHP_CodeSniffer_Tokens::$assignmentTokens[$this->_tokens[$tokenBefore]['code']]) === true) {
Chris@0 3210 // This is directly after an assignment. It's a reference. Even if
Chris@0 3211 // it is part of an operation, the other tests will handle it.
Chris@0 3212 return true;
Chris@0 3213 }
Chris@0 3214
Chris@0 3215 if (isset($this->_tokens[$stackPtr]['nested_parenthesis']) === true) {
Chris@0 3216 $brackets = $this->_tokens[$stackPtr]['nested_parenthesis'];
Chris@0 3217 $lastBracket = array_pop($brackets);
Chris@0 3218 if (isset($this->_tokens[$lastBracket]['parenthesis_owner']) === true) {
Chris@0 3219 $owner = $this->_tokens[$this->_tokens[$lastBracket]['parenthesis_owner']];
Chris@0 3220 if ($owner['code'] === T_FUNCTION
Chris@0 3221 || $owner['code'] === T_CLOSURE
Chris@0 3222 || $owner['code'] === T_ARRAY
Chris@0 3223 ) {
Chris@0 3224 // Inside a function or array declaration, this is a reference.
Chris@0 3225 return true;
Chris@0 3226 }
Chris@0 3227 } else {
Chris@0 3228 $prev = false;
Chris@0 3229 for ($t = ($this->_tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) {
Chris@0 3230 if ($this->_tokens[$t]['code'] !== T_WHITESPACE) {
Chris@0 3231 $prev = $t;
Chris@0 3232 break;
Chris@0 3233 }
Chris@0 3234 }
Chris@0 3235
Chris@0 3236 if ($prev !== false && $this->_tokens[$prev]['code'] === T_USE) {
Chris@0 3237 return true;
Chris@0 3238 }
Chris@0 3239 }//end if
Chris@0 3240 }//end if
Chris@0 3241
Chris@0 3242 $tokenAfter = $this->findNext(
Chris@0 3243 PHP_CodeSniffer_Tokens::$emptyTokens,
Chris@0 3244 ($stackPtr + 1),
Chris@0 3245 null,
Chris@0 3246 true
Chris@0 3247 );
Chris@0 3248
Chris@0 3249 if ($this->_tokens[$tokenAfter]['code'] === T_VARIABLE
Chris@0 3250 && ($this->_tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS
Chris@0 3251 || $this->_tokens[$tokenBefore]['code'] === T_COMMA)
Chris@0 3252 ) {
Chris@0 3253 return true;
Chris@0 3254 }
Chris@0 3255
Chris@0 3256 return false;
Chris@0 3257
Chris@0 3258 }//end isReference()
Chris@0 3259
Chris@0 3260
Chris@0 3261 /**
Chris@0 3262 * Returns the content of the tokens from the specified start position in
Chris@0 3263 * the token stack for the specified length.
Chris@0 3264 *
Chris@0 3265 * @param int $start The position to start from in the token stack.
Chris@0 3266 * @param int $length The length of tokens to traverse from the start pos.
Chris@0 3267 *
Chris@0 3268 * @return string The token contents.
Chris@0 3269 */
Chris@0 3270 public function getTokensAsString($start, $length)
Chris@0 3271 {
Chris@0 3272 $str = '';
Chris@0 3273 $end = ($start + $length);
Chris@0 3274 if ($end > $this->numTokens) {
Chris@0 3275 $end = $this->numTokens;
Chris@0 3276 }
Chris@0 3277
Chris@0 3278 for ($i = $start; $i < $end; $i++) {
Chris@0 3279 $str .= $this->_tokens[$i]['content'];
Chris@0 3280 }
Chris@0 3281
Chris@0 3282 return $str;
Chris@0 3283
Chris@0 3284 }//end getTokensAsString()
Chris@0 3285
Chris@0 3286
Chris@0 3287 /**
Chris@0 3288 * Returns the position of the previous specified token(s).
Chris@0 3289 *
Chris@0 3290 * If a value is specified, the previous token of the specified type(s)
Chris@0 3291 * containing the specified value will be returned.
Chris@0 3292 *
Chris@0 3293 * Returns false if no token can be found.
Chris@0 3294 *
Chris@0 3295 * @param int|array $types The type(s) of tokens to search for.
Chris@0 3296 * @param int $start The position to start searching from in the
Chris@0 3297 * token stack.
Chris@0 3298 * @param int $end The end position to fail if no token is found.
Chris@0 3299 * if not specified or null, end will default to
Chris@0 3300 * the start of the token stack.
Chris@0 3301 * @param bool $exclude If true, find the previous token that are NOT of
Chris@0 3302 * the types specified in $types.
Chris@0 3303 * @param string $value The value that the token(s) must be equal to.
Chris@0 3304 * If value is omitted, tokens with any value will
Chris@0 3305 * be returned.
Chris@0 3306 * @param bool $local If true, tokens outside the current statement
Chris@0 3307 * will not be checked. IE. checking will stop
Chris@0 3308 * at the previous semi-colon found.
Chris@0 3309 *
Chris@0 3310 * @return int|bool
Chris@0 3311 * @see findNext()
Chris@0 3312 */
Chris@0 3313 public function findPrevious(
Chris@0 3314 $types,
Chris@0 3315 $start,
Chris@0 3316 $end=null,
Chris@0 3317 $exclude=false,
Chris@0 3318 $value=null,
Chris@0 3319 $local=false
Chris@0 3320 ) {
Chris@0 3321 $types = (array) $types;
Chris@0 3322
Chris@0 3323 if ($end === null) {
Chris@0 3324 $end = 0;
Chris@0 3325 }
Chris@0 3326
Chris@0 3327 for ($i = $start; $i >= $end; $i--) {
Chris@0 3328 $found = (bool) $exclude;
Chris@0 3329 foreach ($types as $type) {
Chris@0 3330 if ($this->_tokens[$i]['code'] === $type) {
Chris@0 3331 $found = !$exclude;
Chris@0 3332 break;
Chris@0 3333 }
Chris@0 3334 }
Chris@0 3335
Chris@0 3336 if ($found === true) {
Chris@0 3337 if ($value === null) {
Chris@0 3338 return $i;
Chris@0 3339 } else if ($this->_tokens[$i]['content'] === $value) {
Chris@0 3340 return $i;
Chris@0 3341 }
Chris@0 3342 }
Chris@0 3343
Chris@0 3344 if ($local === true) {
Chris@0 3345 if (isset($this->_tokens[$i]['scope_opener']) === true
Chris@0 3346 && $i === $this->_tokens[$i]['scope_closer']
Chris@0 3347 ) {
Chris@0 3348 $i = $this->_tokens[$i]['scope_opener'];
Chris@0 3349 } else if (isset($this->_tokens[$i]['bracket_opener']) === true
Chris@0 3350 && $i === $this->_tokens[$i]['bracket_closer']
Chris@0 3351 ) {
Chris@0 3352 $i = $this->_tokens[$i]['bracket_opener'];
Chris@0 3353 } else if (isset($this->_tokens[$i]['parenthesis_opener']) === true
Chris@0 3354 && $i === $this->_tokens[$i]['parenthesis_closer']
Chris@0 3355 ) {
Chris@0 3356 $i = $this->_tokens[$i]['parenthesis_opener'];
Chris@0 3357 } else if ($this->_tokens[$i]['code'] === T_SEMICOLON) {
Chris@0 3358 break;
Chris@0 3359 }
Chris@0 3360 }
Chris@0 3361 }//end for
Chris@0 3362
Chris@0 3363 return false;
Chris@0 3364
Chris@0 3365 }//end findPrevious()
Chris@0 3366
Chris@0 3367
Chris@0 3368 /**
Chris@0 3369 * Returns the position of the next specified token(s).
Chris@0 3370 *
Chris@0 3371 * If a value is specified, the next token of the specified type(s)
Chris@0 3372 * containing the specified value will be returned.
Chris@0 3373 *
Chris@0 3374 * Returns false if no token can be found.
Chris@0 3375 *
Chris@0 3376 * @param int|array $types The type(s) of tokens to search for.
Chris@0 3377 * @param int $start The position to start searching from in the
Chris@0 3378 * token stack.
Chris@0 3379 * @param int $end The end position to fail if no token is found.
Chris@0 3380 * if not specified or null, end will default to
Chris@0 3381 * the end of the token stack.
Chris@0 3382 * @param bool $exclude If true, find the next token that is NOT of
Chris@0 3383 * a type specified in $types.
Chris@0 3384 * @param string $value The value that the token(s) must be equal to.
Chris@0 3385 * If value is omitted, tokens with any value will
Chris@0 3386 * be returned.
Chris@0 3387 * @param bool $local If true, tokens outside the current statement
Chris@0 3388 * will not be checked. i.e., checking will stop
Chris@0 3389 * at the next semi-colon found.
Chris@0 3390 *
Chris@0 3391 * @return int|bool
Chris@0 3392 * @see findPrevious()
Chris@0 3393 */
Chris@0 3394 public function findNext(
Chris@0 3395 $types,
Chris@0 3396 $start,
Chris@0 3397 $end=null,
Chris@0 3398 $exclude=false,
Chris@0 3399 $value=null,
Chris@0 3400 $local=false
Chris@0 3401 ) {
Chris@0 3402 $types = (array) $types;
Chris@0 3403
Chris@0 3404 if ($end === null || $end > $this->numTokens) {
Chris@0 3405 $end = $this->numTokens;
Chris@0 3406 }
Chris@0 3407
Chris@0 3408 for ($i = $start; $i < $end; $i++) {
Chris@0 3409 $found = (bool) $exclude;
Chris@0 3410 foreach ($types as $type) {
Chris@0 3411 if ($this->_tokens[$i]['code'] === $type) {
Chris@0 3412 $found = !$exclude;
Chris@0 3413 break;
Chris@0 3414 }
Chris@0 3415 }
Chris@0 3416
Chris@0 3417 if ($found === true) {
Chris@0 3418 if ($value === null) {
Chris@0 3419 return $i;
Chris@0 3420 } else if ($this->_tokens[$i]['content'] === $value) {
Chris@0 3421 return $i;
Chris@0 3422 }
Chris@0 3423 }
Chris@0 3424
Chris@0 3425 if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) {
Chris@0 3426 break;
Chris@0 3427 }
Chris@0 3428 }//end for
Chris@0 3429
Chris@0 3430 return false;
Chris@0 3431
Chris@0 3432 }//end findNext()
Chris@0 3433
Chris@0 3434
Chris@0 3435 /**
Chris@0 3436 * Returns the position of the first non-whitespace token in a statement.
Chris@0 3437 *
Chris@0 3438 * @param int $start The position to start searching from in the token stack.
Chris@0 3439 * @param int|array $ignore Token types that should not be considered stop points.
Chris@0 3440 *
Chris@0 3441 * @return int
Chris@0 3442 */
Chris@0 3443 public function findStartOfStatement($start, $ignore=null)
Chris@0 3444 {
Chris@0 3445 $endTokens = PHP_CodeSniffer_Tokens::$blockOpeners;
Chris@0 3446
Chris@0 3447 $endTokens[T_COLON] = true;
Chris@0 3448 $endTokens[T_COMMA] = true;
Chris@0 3449 $endTokens[T_DOUBLE_ARROW] = true;
Chris@0 3450 $endTokens[T_SEMICOLON] = true;
Chris@0 3451 $endTokens[T_OPEN_TAG] = true;
Chris@0 3452 $endTokens[T_CLOSE_TAG] = true;
Chris@0 3453 $endTokens[T_OPEN_SHORT_ARRAY] = true;
Chris@0 3454
Chris@0 3455 if ($ignore !== null) {
Chris@0 3456 $ignore = (array) $ignore;
Chris@0 3457 foreach ($ignore as $code) {
Chris@0 3458 if (isset($endTokens[$code]) === true) {
Chris@0 3459 unset($endTokens[$code]);
Chris@0 3460 }
Chris@0 3461 }
Chris@0 3462 }
Chris@0 3463
Chris@0 3464 $lastNotEmpty = $start;
Chris@0 3465
Chris@0 3466 for ($i = $start; $i >= 0; $i--) {
Chris@0 3467 if (isset($endTokens[$this->_tokens[$i]['code']]) === true) {
Chris@0 3468 // Found the end of the previous statement.
Chris@0 3469 return $lastNotEmpty;
Chris@0 3470 }
Chris@0 3471
Chris@0 3472 if (isset($this->_tokens[$i]['scope_opener']) === true
Chris@0 3473 && $i === $this->_tokens[$i]['scope_closer']
Chris@0 3474 ) {
Chris@0 3475 // Found the end of the previous scope block.
Chris@0 3476 return $lastNotEmpty;
Chris@0 3477 }
Chris@0 3478
Chris@0 3479 // Skip nested statements.
Chris@0 3480 if (isset($this->_tokens[$i]['bracket_opener']) === true
Chris@0 3481 && $i === $this->_tokens[$i]['bracket_closer']
Chris@0 3482 ) {
Chris@0 3483 $i = $this->_tokens[$i]['bracket_opener'];
Chris@0 3484 } else if (isset($this->_tokens[$i]['parenthesis_opener']) === true
Chris@0 3485 && $i === $this->_tokens[$i]['parenthesis_closer']
Chris@0 3486 ) {
Chris@0 3487 $i = $this->_tokens[$i]['parenthesis_opener'];
Chris@0 3488 }
Chris@0 3489
Chris@0 3490 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$this->_tokens[$i]['code']]) === false) {
Chris@0 3491 $lastNotEmpty = $i;
Chris@0 3492 }
Chris@0 3493 }//end for
Chris@0 3494
Chris@0 3495 return 0;
Chris@0 3496
Chris@0 3497 }//end findStartOfStatement()
Chris@0 3498
Chris@0 3499
Chris@0 3500 /**
Chris@0 3501 * Returns the position of the last non-whitespace token in a statement.
Chris@0 3502 *
Chris@0 3503 * @param int $start The position to start searching from in the token stack.
Chris@0 3504 * @param int|array $ignore Token types that should not be considered stop points.
Chris@0 3505 *
Chris@0 3506 * @return int
Chris@0 3507 */
Chris@0 3508 public function findEndOfStatement($start, $ignore=null)
Chris@0 3509 {
Chris@0 3510 $endTokens = array(
Chris@0 3511 T_COLON => true,
Chris@0 3512 T_COMMA => true,
Chris@0 3513 T_DOUBLE_ARROW => true,
Chris@0 3514 T_SEMICOLON => true,
Chris@0 3515 T_CLOSE_PARENTHESIS => true,
Chris@0 3516 T_CLOSE_SQUARE_BRACKET => true,
Chris@0 3517 T_CLOSE_CURLY_BRACKET => true,
Chris@0 3518 T_CLOSE_SHORT_ARRAY => true,
Chris@0 3519 T_OPEN_TAG => true,
Chris@0 3520 T_CLOSE_TAG => true,
Chris@0 3521 );
Chris@0 3522
Chris@0 3523 if ($ignore !== null) {
Chris@0 3524 $ignore = (array) $ignore;
Chris@0 3525 foreach ($ignore as $code) {
Chris@0 3526 if (isset($endTokens[$code]) === true) {
Chris@0 3527 unset($endTokens[$code]);
Chris@0 3528 }
Chris@0 3529 }
Chris@0 3530 }
Chris@0 3531
Chris@0 3532 $lastNotEmpty = $start;
Chris@0 3533
Chris@0 3534 for ($i = $start; $i < $this->numTokens; $i++) {
Chris@0 3535 if ($i !== $start && isset($endTokens[$this->_tokens[$i]['code']]) === true) {
Chris@0 3536 // Found the end of the statement.
Chris@0 3537 if ($this->_tokens[$i]['code'] === T_CLOSE_PARENTHESIS
Chris@0 3538 || $this->_tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
Chris@0 3539 || $this->_tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
Chris@0 3540 || $this->_tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
Chris@0 3541 || $this->_tokens[$i]['code'] === T_OPEN_TAG
Chris@0 3542 || $this->_tokens[$i]['code'] === T_CLOSE_TAG
Chris@0 3543 ) {
Chris@0 3544 return $lastNotEmpty;
Chris@0 3545 }
Chris@0 3546
Chris@0 3547 return $i;
Chris@0 3548 }
Chris@0 3549
Chris@0 3550 // Skip nested statements.
Chris@0 3551 if (isset($this->_tokens[$i]['scope_closer']) === true
Chris@0 3552 && ($i === $this->_tokens[$i]['scope_opener']
Chris@0 3553 || $i === $this->_tokens[$i]['scope_condition'])
Chris@0 3554 ) {
Chris@0 3555 $i = $this->_tokens[$i]['scope_closer'];
Chris@0 3556 } else if (isset($this->_tokens[$i]['bracket_closer']) === true
Chris@0 3557 && $i === $this->_tokens[$i]['bracket_opener']
Chris@0 3558 ) {
Chris@0 3559 $i = $this->_tokens[$i]['bracket_closer'];
Chris@0 3560 } else if (isset($this->_tokens[$i]['parenthesis_closer']) === true
Chris@0 3561 && $i === $this->_tokens[$i]['parenthesis_opener']
Chris@0 3562 ) {
Chris@0 3563 $i = $this->_tokens[$i]['parenthesis_closer'];
Chris@0 3564 }
Chris@0 3565
Chris@0 3566 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$this->_tokens[$i]['code']]) === false) {
Chris@0 3567 $lastNotEmpty = $i;
Chris@0 3568 }
Chris@0 3569 }//end for
Chris@0 3570
Chris@0 3571 return ($this->numTokens - 1);
Chris@0 3572
Chris@0 3573 }//end findEndOfStatement()
Chris@0 3574
Chris@0 3575
Chris@0 3576 /**
Chris@0 3577 * Returns the position of the first token on a line, matching given type.
Chris@0 3578 *
Chris@0 3579 * Returns false if no token can be found.
Chris@0 3580 *
Chris@0 3581 * @param int|array $types The type(s) of tokens to search for.
Chris@0 3582 * @param int $start The position to start searching from in the
Chris@0 3583 * token stack. The first token matching on
Chris@0 3584 * this line before this token will be returned.
Chris@0 3585 * @param bool $exclude If true, find the token that is NOT of
Chris@0 3586 * the types specified in $types.
Chris@0 3587 * @param string $value The value that the token must be equal to.
Chris@0 3588 * If value is omitted, tokens with any value will
Chris@0 3589 * be returned.
Chris@0 3590 *
Chris@0 3591 * @return int | bool
Chris@0 3592 */
Chris@0 3593 public function findFirstOnLine($types, $start, $exclude=false, $value=null)
Chris@0 3594 {
Chris@0 3595 if (is_array($types) === false) {
Chris@0 3596 $types = array($types);
Chris@0 3597 }
Chris@0 3598
Chris@0 3599 $foundToken = false;
Chris@0 3600
Chris@0 3601 for ($i = $start; $i >= 0; $i--) {
Chris@0 3602 if ($this->_tokens[$i]['line'] < $this->_tokens[$start]['line']) {
Chris@0 3603 break;
Chris@0 3604 }
Chris@0 3605
Chris@0 3606 $found = $exclude;
Chris@0 3607 foreach ($types as $type) {
Chris@0 3608 if ($exclude === false) {
Chris@0 3609 if ($this->_tokens[$i]['code'] === $type) {
Chris@0 3610 $found = true;
Chris@0 3611 break;
Chris@0 3612 }
Chris@0 3613 } else {
Chris@0 3614 if ($this->_tokens[$i]['code'] === $type) {
Chris@0 3615 $found = false;
Chris@0 3616 break;
Chris@0 3617 }
Chris@0 3618 }
Chris@0 3619 }
Chris@0 3620
Chris@0 3621 if ($found === true) {
Chris@0 3622 if ($value === null) {
Chris@0 3623 $foundToken = $i;
Chris@0 3624 } else if ($this->_tokens[$i]['content'] === $value) {
Chris@0 3625 $foundToken = $i;
Chris@0 3626 }
Chris@0 3627 }
Chris@0 3628 }//end for
Chris@0 3629
Chris@0 3630 return $foundToken;
Chris@0 3631
Chris@0 3632 }//end findFirstOnLine()
Chris@0 3633
Chris@0 3634
Chris@0 3635 /**
Chris@0 3636 * Determine if the passed token has a condition of one of the passed types.
Chris@0 3637 *
Chris@0 3638 * @param int $stackPtr The position of the token we are checking.
Chris@0 3639 * @param int|array $types The type(s) of tokens to search for.
Chris@0 3640 *
Chris@0 3641 * @return boolean
Chris@0 3642 */
Chris@0 3643 public function hasCondition($stackPtr, $types)
Chris@0 3644 {
Chris@0 3645 // Check for the existence of the token.
Chris@0 3646 if (isset($this->_tokens[$stackPtr]) === false) {
Chris@0 3647 return false;
Chris@0 3648 }
Chris@0 3649
Chris@0 3650 // Make sure the token has conditions.
Chris@0 3651 if (isset($this->_tokens[$stackPtr]['conditions']) === false) {
Chris@0 3652 return false;
Chris@0 3653 }
Chris@0 3654
Chris@0 3655 $types = (array) $types;
Chris@0 3656 $conditions = $this->_tokens[$stackPtr]['conditions'];
Chris@0 3657
Chris@0 3658 foreach ($types as $type) {
Chris@0 3659 if (in_array($type, $conditions) === true) {
Chris@0 3660 // We found a token with the required type.
Chris@0 3661 return true;
Chris@0 3662 }
Chris@0 3663 }
Chris@0 3664
Chris@0 3665 return false;
Chris@0 3666
Chris@0 3667 }//end hasCondition()
Chris@0 3668
Chris@0 3669
Chris@0 3670 /**
Chris@0 3671 * Return the position of the condition for the passed token.
Chris@0 3672 *
Chris@0 3673 * Returns FALSE if the token does not have the condition.
Chris@0 3674 *
Chris@0 3675 * @param int $stackPtr The position of the token we are checking.
Chris@0 3676 * @param int $type The type of token to search for.
Chris@0 3677 *
Chris@0 3678 * @return int
Chris@0 3679 */
Chris@0 3680 public function getCondition($stackPtr, $type)
Chris@0 3681 {
Chris@0 3682 // Check for the existence of the token.
Chris@0 3683 if (isset($this->_tokens[$stackPtr]) === false) {
Chris@0 3684 return false;
Chris@0 3685 }
Chris@0 3686
Chris@0 3687 // Make sure the token has conditions.
Chris@0 3688 if (isset($this->_tokens[$stackPtr]['conditions']) === false) {
Chris@0 3689 return false;
Chris@0 3690 }
Chris@0 3691
Chris@0 3692 $conditions = $this->_tokens[$stackPtr]['conditions'];
Chris@0 3693 foreach ($conditions as $token => $condition) {
Chris@0 3694 if ($condition === $type) {
Chris@0 3695 return $token;
Chris@0 3696 }
Chris@0 3697 }
Chris@0 3698
Chris@0 3699 return false;
Chris@0 3700
Chris@0 3701 }//end getCondition()
Chris@0 3702
Chris@0 3703
Chris@0 3704 /**
Chris@0 3705 * Returns the name of the class that the specified class extends.
Chris@0 3706 *
Chris@0 3707 * Returns FALSE on error or if there is no extended class name.
Chris@0 3708 *
Chris@0 3709 * @param int $stackPtr The stack position of the class.
Chris@0 3710 *
Chris@0 3711 * @return string
Chris@0 3712 */
Chris@0 3713 public function findExtendedClassName($stackPtr)
Chris@0 3714 {
Chris@0 3715 // Check for the existence of the token.
Chris@0 3716 if (isset($this->_tokens[$stackPtr]) === false) {
Chris@0 3717 return false;
Chris@0 3718 }
Chris@0 3719
Chris@0 3720 if ($this->_tokens[$stackPtr]['code'] !== T_CLASS
Chris@0 3721 && $this->_tokens[$stackPtr]['code'] !== T_ANON_CLASS
Chris@0 3722 ) {
Chris@0 3723 return false;
Chris@0 3724 }
Chris@0 3725
Chris@0 3726 if (isset($this->_tokens[$stackPtr]['scope_closer']) === false) {
Chris@0 3727 return false;
Chris@0 3728 }
Chris@0 3729
Chris@0 3730 $classCloserIndex = $this->_tokens[$stackPtr]['scope_closer'];
Chris@0 3731 $extendsIndex = $this->findNext(T_EXTENDS, $stackPtr, $classCloserIndex);
Chris@0 3732 if (false === $extendsIndex) {
Chris@0 3733 return false;
Chris@0 3734 }
Chris@0 3735
Chris@0 3736 $find = array(
Chris@0 3737 T_NS_SEPARATOR,
Chris@0 3738 T_STRING,
Chris@0 3739 T_WHITESPACE,
Chris@0 3740 );
Chris@0 3741
Chris@0 3742 $end = $this->findNext($find, ($extendsIndex + 1), $classCloserIndex, true);
Chris@0 3743 $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
Chris@0 3744 $name = trim($name);
Chris@0 3745
Chris@0 3746 if ($name === '') {
Chris@0 3747 return false;
Chris@0 3748 }
Chris@0 3749
Chris@0 3750 return $name;
Chris@0 3751
Chris@0 3752 }//end findExtendedClassName()
Chris@0 3753
Chris@0 3754
Chris@0 3755 /**
Chris@0 3756 * Returns the name(s) of the interface(s) that the specified class implements.
Chris@0 3757 *
Chris@0 3758 * Returns FALSE on error or if there are no implemented interface names.
Chris@0 3759 *
Chris@0 3760 * @param int $stackPtr The stack position of the class.
Chris@0 3761 *
Chris@0 3762 * @return array|false
Chris@0 3763 */
Chris@0 3764 public function findImplementedInterfaceNames($stackPtr)
Chris@0 3765 {
Chris@0 3766 // Check for the existence of the token.
Chris@0 3767 if (isset($this->_tokens[$stackPtr]) === false) {
Chris@0 3768 return false;
Chris@0 3769 }
Chris@0 3770
Chris@0 3771 if ($this->_tokens[$stackPtr]['code'] !== T_CLASS
Chris@0 3772 && $this->_tokens[$stackPtr]['code'] !== T_ANON_CLASS
Chris@0 3773 ) {
Chris@0 3774 return false;
Chris@0 3775 }
Chris@0 3776
Chris@0 3777 if (isset($this->_tokens[$stackPtr]['scope_closer']) === false) {
Chris@0 3778 return false;
Chris@0 3779 }
Chris@0 3780
Chris@0 3781 $classOpenerIndex = $this->_tokens[$stackPtr]['scope_opener'];
Chris@0 3782 $implementsIndex = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
Chris@0 3783 if ($implementsIndex === false) {
Chris@0 3784 return false;
Chris@0 3785 }
Chris@0 3786
Chris@0 3787 $find = array(
Chris@0 3788 T_NS_SEPARATOR,
Chris@0 3789 T_STRING,
Chris@0 3790 T_WHITESPACE,
Chris@0 3791 T_COMMA,
Chris@0 3792 );
Chris@0 3793
Chris@0 3794 $end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
Chris@0 3795 $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
Chris@0 3796 $name = trim($name);
Chris@0 3797
Chris@0 3798 if ($name === '') {
Chris@0 3799 return false;
Chris@0 3800 } else {
Chris@0 3801 $names = explode(',', $name);
Chris@0 3802 $names = array_map('trim', $names);
Chris@0 3803 return $names;
Chris@0 3804 }
Chris@0 3805
Chris@0 3806 }//end findImplementedInterfaceNames()
Chris@0 3807
Chris@0 3808
Chris@0 3809 }//end class