annotate vendor/squizlabs/php_codesniffer/src/Files/File.php @ 17:129ea1e6d783

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents
children af1871eacc83
rev   line source
Chris@17 1 <?php
Chris@17 2 /**
Chris@17 3 * Represents a piece of content being checked during the run.
Chris@17 4 *
Chris@17 5 * @author Greg Sherwood <gsherwood@squiz.net>
Chris@17 6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
Chris@17 7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
Chris@17 8 */
Chris@17 9
Chris@17 10 namespace PHP_CodeSniffer\Files;
Chris@17 11
Chris@17 12 use PHP_CodeSniffer\Ruleset;
Chris@17 13 use PHP_CodeSniffer\Config;
Chris@17 14 use PHP_CodeSniffer\Fixer;
Chris@17 15 use PHP_CodeSniffer\Util;
Chris@17 16 use PHP_CodeSniffer\Exceptions\RuntimeException;
Chris@17 17 use PHP_CodeSniffer\Exceptions\TokenizerException;
Chris@17 18
Chris@17 19 class File
Chris@17 20 {
Chris@17 21
Chris@17 22 /**
Chris@17 23 * The absolute path to the file associated with this object.
Chris@17 24 *
Chris@17 25 * @var string
Chris@17 26 */
Chris@17 27 public $path = '';
Chris@17 28
Chris@17 29 /**
Chris@17 30 * The absolute path to the file associated with this object.
Chris@17 31 *
Chris@17 32 * @var string
Chris@17 33 */
Chris@17 34 protected $content = '';
Chris@17 35
Chris@17 36 /**
Chris@17 37 * The config data for the run.
Chris@17 38 *
Chris@17 39 * @var \PHP_CodeSniffer\Config
Chris@17 40 */
Chris@17 41 public $config = null;
Chris@17 42
Chris@17 43 /**
Chris@17 44 * The ruleset used for the run.
Chris@17 45 *
Chris@17 46 * @var \PHP_CodeSniffer\Ruleset
Chris@17 47 */
Chris@17 48 public $ruleset = null;
Chris@17 49
Chris@17 50 /**
Chris@17 51 * If TRUE, the entire file is being ignored.
Chris@17 52 *
Chris@17 53 * @var boolean
Chris@17 54 */
Chris@17 55 public $ignored = false;
Chris@17 56
Chris@17 57 /**
Chris@17 58 * The EOL character this file uses.
Chris@17 59 *
Chris@17 60 * @var string
Chris@17 61 */
Chris@17 62 public $eolChar = '';
Chris@17 63
Chris@17 64 /**
Chris@17 65 * The Fixer object to control fixing errors.
Chris@17 66 *
Chris@17 67 * @var \PHP_CodeSniffer\Fixer
Chris@17 68 */
Chris@17 69 public $fixer = null;
Chris@17 70
Chris@17 71 /**
Chris@17 72 * The tokenizer being used for this file.
Chris@17 73 *
Chris@17 74 * @var \PHP_CodeSniffer\Tokenizers\Tokenizer
Chris@17 75 */
Chris@17 76 public $tokenizer = null;
Chris@17 77
Chris@17 78 /**
Chris@17 79 * The name of the tokenizer being used for this file.
Chris@17 80 *
Chris@17 81 * @var string
Chris@17 82 */
Chris@17 83 public $tokenizerType = 'PHP';
Chris@17 84
Chris@17 85 /**
Chris@17 86 * Was the file loaded from cache?
Chris@17 87 *
Chris@17 88 * If TRUE, the file was loaded from a local cache.
Chris@17 89 * If FALSE, the file was tokenized and processed fully.
Chris@17 90 *
Chris@17 91 * @var boolean
Chris@17 92 */
Chris@17 93 public $fromCache = false;
Chris@17 94
Chris@17 95 /**
Chris@17 96 * The number of tokens in this file.
Chris@17 97 *
Chris@17 98 * Stored here to save calling count() everywhere.
Chris@17 99 *
Chris@17 100 * @var integer
Chris@17 101 */
Chris@17 102 public $numTokens = 0;
Chris@17 103
Chris@17 104 /**
Chris@17 105 * The tokens stack map.
Chris@17 106 *
Chris@17 107 * @var array
Chris@17 108 */
Chris@17 109 protected $tokens = [];
Chris@17 110
Chris@17 111 /**
Chris@17 112 * The errors raised from sniffs.
Chris@17 113 *
Chris@17 114 * @var array
Chris@17 115 * @see getErrors()
Chris@17 116 */
Chris@17 117 protected $errors = [];
Chris@17 118
Chris@17 119 /**
Chris@17 120 * The warnings raised from sniffs.
Chris@17 121 *
Chris@17 122 * @var array
Chris@17 123 * @see getWarnings()
Chris@17 124 */
Chris@17 125 protected $warnings = [];
Chris@17 126
Chris@17 127 /**
Chris@17 128 * The metrics recorded by sniffs.
Chris@17 129 *
Chris@17 130 * @var array
Chris@17 131 * @see getMetrics()
Chris@17 132 */
Chris@17 133 protected $metrics = [];
Chris@17 134
Chris@17 135 /**
Chris@17 136 * The metrics recorded for each token.
Chris@17 137 *
Chris@17 138 * Stops the same metric being recorded for the same token twice.
Chris@17 139 *
Chris@17 140 * @var array
Chris@17 141 * @see getMetrics()
Chris@17 142 */
Chris@17 143 private $metricTokens = [];
Chris@17 144
Chris@17 145 /**
Chris@17 146 * The total number of errors raised.
Chris@17 147 *
Chris@17 148 * @var integer
Chris@17 149 */
Chris@17 150 protected $errorCount = 0;
Chris@17 151
Chris@17 152 /**
Chris@17 153 * The total number of warnings raised.
Chris@17 154 *
Chris@17 155 * @var integer
Chris@17 156 */
Chris@17 157 protected $warningCount = 0;
Chris@17 158
Chris@17 159 /**
Chris@17 160 * The total number of errors and warnings that can be fixed.
Chris@17 161 *
Chris@17 162 * @var integer
Chris@17 163 */
Chris@17 164 protected $fixableCount = 0;
Chris@17 165
Chris@17 166 /**
Chris@17 167 * The total number of errors and warnings that were fixed.
Chris@17 168 *
Chris@17 169 * @var integer
Chris@17 170 */
Chris@17 171 protected $fixedCount = 0;
Chris@17 172
Chris@17 173 /**
Chris@17 174 * An array of sniffs that are being ignored.
Chris@17 175 *
Chris@17 176 * @var array
Chris@17 177 */
Chris@17 178 protected $ignoredListeners = [];
Chris@17 179
Chris@17 180 /**
Chris@17 181 * An array of message codes that are being ignored.
Chris@17 182 *
Chris@17 183 * @var array
Chris@17 184 */
Chris@17 185 protected $ignoredCodes = [];
Chris@17 186
Chris@17 187 /**
Chris@17 188 * An array of sniffs listening to this file's processing.
Chris@17 189 *
Chris@17 190 * @var \PHP_CodeSniffer\Sniffs\Sniff[]
Chris@17 191 */
Chris@17 192 protected $listeners = [];
Chris@17 193
Chris@17 194 /**
Chris@17 195 * The class name of the sniff currently processing the file.
Chris@17 196 *
Chris@17 197 * @var string
Chris@17 198 */
Chris@17 199 protected $activeListener = '';
Chris@17 200
Chris@17 201 /**
Chris@17 202 * An array of sniffs being processed and how long they took.
Chris@17 203 *
Chris@17 204 * @var array
Chris@17 205 */
Chris@17 206 protected $listenerTimes = [];
Chris@17 207
Chris@17 208 /**
Chris@17 209 * A cache of often used config settings to improve performance.
Chris@17 210 *
Chris@17 211 * Storing them here saves 10k+ calls to __get() in the Config class.
Chris@17 212 *
Chris@17 213 * @var array
Chris@17 214 */
Chris@17 215 protected $configCache = [];
Chris@17 216
Chris@17 217
Chris@17 218 /**
Chris@17 219 * Constructs a file.
Chris@17 220 *
Chris@17 221 * @param string $path The absolute path to the file to process.
Chris@17 222 * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
Chris@17 223 * @param \PHP_CodeSniffer\Config $config The config data for the run.
Chris@17 224 *
Chris@17 225 * @return void
Chris@17 226 */
Chris@17 227 public function __construct($path, Ruleset $ruleset, Config $config)
Chris@17 228 {
Chris@17 229 $this->path = $path;
Chris@17 230 $this->ruleset = $ruleset;
Chris@17 231 $this->config = $config;
Chris@17 232 $this->fixer = new Fixer();
Chris@17 233
Chris@17 234 $parts = explode('.', $path);
Chris@17 235 $extension = array_pop($parts);
Chris@17 236 if (isset($config->extensions[$extension]) === true) {
Chris@17 237 $this->tokenizerType = $config->extensions[$extension];
Chris@17 238 } else {
Chris@17 239 // Revert to default.
Chris@17 240 $this->tokenizerType = 'PHP';
Chris@17 241 }
Chris@17 242
Chris@17 243 $this->configCache['cache'] = $this->config->cache;
Chris@17 244 $this->configCache['sniffs'] = array_map('strtolower', $this->config->sniffs);
Chris@17 245 $this->configCache['exclude'] = array_map('strtolower', $this->config->exclude);
Chris@17 246 $this->configCache['errorSeverity'] = $this->config->errorSeverity;
Chris@17 247 $this->configCache['warningSeverity'] = $this->config->warningSeverity;
Chris@17 248 $this->configCache['recordErrors'] = $this->config->recordErrors;
Chris@17 249 $this->configCache['ignorePatterns'] = $this->ruleset->ignorePatterns;
Chris@17 250 $this->configCache['includePatterns'] = $this->ruleset->includePatterns;
Chris@17 251
Chris@17 252 }//end __construct()
Chris@17 253
Chris@17 254
Chris@17 255 /**
Chris@17 256 * Set the content of the file.
Chris@17 257 *
Chris@17 258 * Setting the content also calculates the EOL char being used.
Chris@17 259 *
Chris@17 260 * @param string $content The file content.
Chris@17 261 *
Chris@17 262 * @return void
Chris@17 263 */
Chris@17 264 public function setContent($content)
Chris@17 265 {
Chris@17 266 $this->content = $content;
Chris@17 267 $this->tokens = [];
Chris@17 268
Chris@17 269 try {
Chris@17 270 $this->eolChar = Util\Common::detectLineEndings($content);
Chris@17 271 } catch (RuntimeException $e) {
Chris@17 272 $this->addWarningOnLine($e->getMessage(), 1, 'Internal.DetectLineEndings');
Chris@17 273 return;
Chris@17 274 }
Chris@17 275
Chris@17 276 }//end setContent()
Chris@17 277
Chris@17 278
Chris@17 279 /**
Chris@17 280 * Reloads the content of the file.
Chris@17 281 *
Chris@17 282 * By default, we have no idea where our content comes from,
Chris@17 283 * so we can't do anything.
Chris@17 284 *
Chris@17 285 * @return void
Chris@17 286 */
Chris@17 287 public function reloadContent()
Chris@17 288 {
Chris@17 289
Chris@17 290 }//end reloadContent()
Chris@17 291
Chris@17 292
Chris@17 293 /**
Chris@17 294 * Disables caching of this file.
Chris@17 295 *
Chris@17 296 * @return void
Chris@17 297 */
Chris@17 298 public function disableCaching()
Chris@17 299 {
Chris@17 300 $this->configCache['cache'] = false;
Chris@17 301
Chris@17 302 }//end disableCaching()
Chris@17 303
Chris@17 304
Chris@17 305 /**
Chris@17 306 * Starts the stack traversal and tells listeners when tokens are found.
Chris@17 307 *
Chris@17 308 * @return void
Chris@17 309 */
Chris@17 310 public function process()
Chris@17 311 {
Chris@17 312 if ($this->ignored === true) {
Chris@17 313 return;
Chris@17 314 }
Chris@17 315
Chris@17 316 $this->errors = [];
Chris@17 317 $this->warnings = [];
Chris@17 318 $this->errorCount = 0;
Chris@17 319 $this->warningCount = 0;
Chris@17 320 $this->fixableCount = 0;
Chris@17 321
Chris@17 322 $this->parse();
Chris@17 323
Chris@17 324 // Check if tokenizer errors cause this file to be ignored.
Chris@17 325 if ($this->ignored === true) {
Chris@17 326 return;
Chris@17 327 }
Chris@17 328
Chris@17 329 $this->fixer->startFile($this);
Chris@17 330
Chris@17 331 if (PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@17 332 echo "\t*** START TOKEN PROCESSING ***".PHP_EOL;
Chris@17 333 }
Chris@17 334
Chris@17 335 $foundCode = false;
Chris@17 336 $listenerIgnoreTo = [];
Chris@17 337 $inTests = defined('PHP_CODESNIFFER_IN_TESTS');
Chris@17 338 $checkAnnotations = $this->config->annotations;
Chris@17 339
Chris@17 340 // Foreach of the listeners that have registered to listen for this
Chris@17 341 // token, get them to process it.
Chris@17 342 foreach ($this->tokens as $stackPtr => $token) {
Chris@17 343 // Check for ignored lines.
Chris@17 344 if ($checkAnnotations === true
Chris@17 345 && ($token['code'] === T_COMMENT
Chris@17 346 || $token['code'] === T_PHPCS_IGNORE_FILE
Chris@17 347 || $token['code'] === T_PHPCS_SET
Chris@17 348 || $token['code'] === T_DOC_COMMENT_STRING
Chris@17 349 || $token['code'] === T_DOC_COMMENT_TAG
Chris@17 350 || ($inTests === true && $token['code'] === T_INLINE_HTML))
Chris@17 351 ) {
Chris@17 352 $commentText = ltrim($this->tokens[$stackPtr]['content'], ' /*');
Chris@17 353 $commentTextLower = strtolower($commentText);
Chris@17 354 if (strpos($commentText, '@codingStandards') !== false) {
Chris@17 355 if (strpos($commentText, '@codingStandardsIgnoreFile') !== false) {
Chris@17 356 // Ignoring the whole file, just a little late.
Chris@17 357 $this->errors = [];
Chris@17 358 $this->warnings = [];
Chris@17 359 $this->errorCount = 0;
Chris@17 360 $this->warningCount = 0;
Chris@17 361 $this->fixableCount = 0;
Chris@17 362 return;
Chris@17 363 } else if (strpos($commentText, '@codingStandardsChangeSetting') !== false) {
Chris@17 364 $start = strpos($commentText, '@codingStandardsChangeSetting');
Chris@17 365 $comment = substr($commentText, ($start + 30));
Chris@17 366 $parts = explode(' ', $comment);
Chris@17 367 if (count($parts) >= 2) {
Chris@17 368 $sniffParts = explode('.', $parts[0]);
Chris@17 369 if (count($sniffParts) >= 3) {
Chris@17 370 // If the sniff code is not known to us, it has not been registered in this run.
Chris@17 371 // But don't throw an error as it could be there for a different standard to use.
Chris@17 372 if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
Chris@17 373 $listenerCode = array_shift($parts);
Chris@17 374 $propertyCode = array_shift($parts);
Chris@17 375 $propertyValue = rtrim(implode(' ', $parts), " */\r\n");
Chris@17 376 $listenerClass = $this->ruleset->sniffCodes[$listenerCode];
Chris@17 377 $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
Chris@17 378 }
Chris@17 379 }
Chris@17 380 }
Chris@17 381 }//end if
Chris@17 382 } else if (substr($commentTextLower, 0, 16) === 'phpcs:ignorefile'
Chris@17 383 || substr($commentTextLower, 0, 17) === '@phpcs:ignorefile'
Chris@17 384 ) {
Chris@17 385 // Ignoring the whole file, just a little late.
Chris@17 386 $this->errors = [];
Chris@17 387 $this->warnings = [];
Chris@17 388 $this->errorCount = 0;
Chris@17 389 $this->warningCount = 0;
Chris@17 390 $this->fixableCount = 0;
Chris@17 391 return;
Chris@17 392 } else if (substr($commentTextLower, 0, 9) === 'phpcs:set'
Chris@17 393 || substr($commentTextLower, 0, 10) === '@phpcs:set'
Chris@17 394 ) {
Chris@17 395 // If the @phpcs: syntax is being used, strip the @ to make
Chris@17 396 // comparisons easier.
Chris@17 397 if ($commentText[0] === '@') {
Chris@17 398 $commentText = substr($commentText, 1);
Chris@17 399 }
Chris@17 400
Chris@17 401 // Need to maintain case here, to get the correct sniff code.
Chris@17 402 $parts = explode(' ', substr($commentText, 10));
Chris@17 403 if (count($parts) >= 2) {
Chris@17 404 $sniffParts = explode('.', $parts[0]);
Chris@17 405 if (count($sniffParts) >= 3) {
Chris@17 406 // If the sniff code is not known to us, it has not been registered in this run.
Chris@17 407 // But don't throw an error as it could be there for a different standard to use.
Chris@17 408 if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
Chris@17 409 $listenerCode = array_shift($parts);
Chris@17 410 $propertyCode = array_shift($parts);
Chris@17 411 $propertyValue = rtrim(implode(' ', $parts), " */\r\n");
Chris@17 412 $listenerClass = $this->ruleset->sniffCodes[$listenerCode];
Chris@17 413 $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
Chris@17 414 }
Chris@17 415 }
Chris@17 416 }
Chris@17 417 }//end if
Chris@17 418 }//end if
Chris@17 419
Chris@17 420 if (PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@17 421 $type = $token['type'];
Chris@17 422 $content = Util\Common::prepareForOutput($token['content']);
Chris@17 423 echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL;
Chris@17 424 }
Chris@17 425
Chris@17 426 if ($token['code'] !== T_INLINE_HTML) {
Chris@17 427 $foundCode = true;
Chris@17 428 }
Chris@17 429
Chris@17 430 if (isset($this->ruleset->tokenListeners[$token['code']]) === false) {
Chris@17 431 continue;
Chris@17 432 }
Chris@17 433
Chris@17 434 foreach ($this->ruleset->tokenListeners[$token['code']] as $listenerData) {
Chris@17 435 if (isset($this->ignoredListeners[$listenerData['class']]) === true
Chris@17 436 || (isset($listenerIgnoreTo[$listenerData['class']]) === true
Chris@17 437 && $listenerIgnoreTo[$listenerData['class']] > $stackPtr)
Chris@17 438 ) {
Chris@17 439 // This sniff is ignoring past this token, or the whole file.
Chris@17 440 continue;
Chris@17 441 }
Chris@17 442
Chris@17 443 // Make sure this sniff supports the tokenizer
Chris@17 444 // we are currently using.
Chris@17 445 $class = $listenerData['class'];
Chris@17 446
Chris@17 447 if (isset($listenerData['tokenizers'][$this->tokenizerType]) === false) {
Chris@17 448 continue;
Chris@17 449 }
Chris@17 450
Chris@17 451 // If the file path matches one of our ignore patterns, skip it.
Chris@17 452 // While there is support for a type of each pattern
Chris@17 453 // (absolute or relative) we don't actually support it here.
Chris@17 454 foreach ($listenerData['ignore'] as $pattern) {
Chris@17 455 // We assume a / directory separator, as do the exclude rules
Chris@17 456 // most developers write, so we need a special case for any system
Chris@17 457 // that is different.
Chris@17 458 if (DIRECTORY_SEPARATOR === '\\') {
Chris@17 459 $pattern = str_replace('/', '\\\\', $pattern);
Chris@17 460 }
Chris@17 461
Chris@17 462 $pattern = '`'.$pattern.'`i';
Chris@17 463 if (preg_match($pattern, $this->path) === 1) {
Chris@17 464 $this->ignoredListeners[$class] = true;
Chris@17 465 continue(2);
Chris@17 466 }
Chris@17 467 }
Chris@17 468
Chris@17 469 // If the file path does not match one of our include patterns, skip it.
Chris@17 470 // While there is support for a type of each pattern
Chris@17 471 // (absolute or relative) we don't actually support it here.
Chris@17 472 if (empty($listenerData['include']) === false) {
Chris@17 473 $included = false;
Chris@17 474 foreach ($listenerData['include'] as $pattern) {
Chris@17 475 // We assume a / directory separator, as do the exclude rules
Chris@17 476 // most developers write, so we need a special case for any system
Chris@17 477 // that is different.
Chris@17 478 if (DIRECTORY_SEPARATOR === '\\') {
Chris@17 479 $pattern = str_replace('/', '\\\\', $pattern);
Chris@17 480 }
Chris@17 481
Chris@17 482 $pattern = '`'.$pattern.'`i';
Chris@17 483 if (preg_match($pattern, $this->path) === 1) {
Chris@17 484 $included = true;
Chris@17 485 break;
Chris@17 486 }
Chris@17 487 }
Chris@17 488
Chris@17 489 if ($included === false) {
Chris@17 490 $this->ignoredListeners[$class] = true;
Chris@17 491 continue;
Chris@17 492 }
Chris@17 493 }//end if
Chris@17 494
Chris@17 495 $this->activeListener = $class;
Chris@17 496
Chris@17 497 if (PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@17 498 $startTime = microtime(true);
Chris@17 499 echo "\t\t\tProcessing ".$this->activeListener.'... ';
Chris@17 500 }
Chris@17 501
Chris@17 502 $ignoreTo = $this->ruleset->sniffs[$class]->process($this, $stackPtr);
Chris@17 503 if ($ignoreTo !== null) {
Chris@17 504 $listenerIgnoreTo[$this->activeListener] = $ignoreTo;
Chris@17 505 }
Chris@17 506
Chris@17 507 if (PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@17 508 $timeTaken = (microtime(true) - $startTime);
Chris@17 509 if (isset($this->listenerTimes[$this->activeListener]) === false) {
Chris@17 510 $this->listenerTimes[$this->activeListener] = 0;
Chris@17 511 }
Chris@17 512
Chris@17 513 $this->listenerTimes[$this->activeListener] += $timeTaken;
Chris@17 514
Chris@17 515 $timeTaken = round(($timeTaken), 4);
Chris@17 516 echo "DONE in $timeTaken seconds".PHP_EOL;
Chris@17 517 }
Chris@17 518
Chris@17 519 $this->activeListener = '';
Chris@17 520 }//end foreach
Chris@17 521 }//end foreach
Chris@17 522
Chris@17 523 // If short open tags are off but the file being checked uses
Chris@17 524 // short open tags, the whole content will be inline HTML
Chris@17 525 // and nothing will be checked. So try and handle this case.
Chris@17 526 // We don't show this error for STDIN because we can't be sure the content
Chris@17 527 // actually came directly from the user. It could be something like
Chris@17 528 // refs from a Git pre-push hook.
Chris@17 529 if ($foundCode === false && $this->tokenizerType === 'PHP' && $this->path !== 'STDIN') {
Chris@17 530 $shortTags = (bool) ini_get('short_open_tag');
Chris@17 531 if ($shortTags === false) {
Chris@17 532 $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@17 533 $this->addWarning($error, null, 'Internal.NoCodeFound');
Chris@17 534 }
Chris@17 535 }
Chris@17 536
Chris@17 537 if (PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@17 538 echo "\t*** END TOKEN PROCESSING ***".PHP_EOL;
Chris@17 539 echo "\t*** START SNIFF PROCESSING REPORT ***".PHP_EOL;
Chris@17 540
Chris@17 541 asort($this->listenerTimes, SORT_NUMERIC);
Chris@17 542 $this->listenerTimes = array_reverse($this->listenerTimes, true);
Chris@17 543 foreach ($this->listenerTimes as $listener => $timeTaken) {
Chris@17 544 echo "\t$listener: ".round(($timeTaken), 4).' secs'.PHP_EOL;
Chris@17 545 }
Chris@17 546
Chris@17 547 echo "\t*** END SNIFF PROCESSING REPORT ***".PHP_EOL;
Chris@17 548 }
Chris@17 549
Chris@17 550 $this->fixedCount += $this->fixer->getFixCount();
Chris@17 551
Chris@17 552 }//end process()
Chris@17 553
Chris@17 554
Chris@17 555 /**
Chris@17 556 * Tokenizes the file and prepares it for the test run.
Chris@17 557 *
Chris@17 558 * @return void
Chris@17 559 */
Chris@17 560 public function parse()
Chris@17 561 {
Chris@17 562 if (empty($this->tokens) === false) {
Chris@17 563 // File has already been parsed.
Chris@17 564 return;
Chris@17 565 }
Chris@17 566
Chris@17 567 try {
Chris@17 568 $tokenizerClass = 'PHP_CodeSniffer\Tokenizers\\'.$this->tokenizerType;
Chris@17 569 $this->tokenizer = new $tokenizerClass($this->content, $this->config, $this->eolChar);
Chris@17 570 $this->tokens = $this->tokenizer->getTokens();
Chris@17 571 } catch (TokenizerException $e) {
Chris@17 572 $this->ignored = true;
Chris@17 573 $this->addWarning($e->getMessage(), null, 'Internal.Tokenizer.Exception');
Chris@17 574 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 575 echo "[$this->tokenizerType => tokenizer error]... ";
Chris@17 576 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 577 echo PHP_EOL;
Chris@17 578 }
Chris@17 579 }
Chris@17 580
Chris@17 581 return;
Chris@17 582 }
Chris@17 583
Chris@17 584 $this->numTokens = count($this->tokens);
Chris@17 585
Chris@17 586 // Check for mixed line endings as these can cause tokenizer errors and we
Chris@17 587 // should let the user know that the results they get may be incorrect.
Chris@17 588 // This is done by removing all backslashes, removing the newline char we
Chris@17 589 // detected, then converting newlines chars into text. If any backslashes
Chris@17 590 // are left at the end, we have additional newline chars in use.
Chris@17 591 $contents = str_replace('\\', '', $this->content);
Chris@17 592 $contents = str_replace($this->eolChar, '', $contents);
Chris@17 593 $contents = str_replace("\n", '\n', $contents);
Chris@17 594 $contents = str_replace("\r", '\r', $contents);
Chris@17 595 if (strpos($contents, '\\') !== false) {
Chris@17 596 $error = 'File has mixed line endings; this may cause incorrect results';
Chris@17 597 $this->addWarningOnLine($error, 1, 'Internal.LineEndings.Mixed');
Chris@17 598 }
Chris@17 599
Chris@17 600 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 601 if ($this->numTokens === 0) {
Chris@17 602 $numLines = 0;
Chris@17 603 } else {
Chris@17 604 $numLines = $this->tokens[($this->numTokens - 1)]['line'];
Chris@17 605 }
Chris@17 606
Chris@17 607 echo "[$this->tokenizerType => $this->numTokens tokens in $numLines lines]... ";
Chris@17 608 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 609 echo PHP_EOL;
Chris@17 610 }
Chris@17 611 }
Chris@17 612
Chris@17 613 }//end parse()
Chris@17 614
Chris@17 615
Chris@17 616 /**
Chris@17 617 * Returns the token stack for this file.
Chris@17 618 *
Chris@17 619 * @return array
Chris@17 620 */
Chris@17 621 public function getTokens()
Chris@17 622 {
Chris@17 623 return $this->tokens;
Chris@17 624
Chris@17 625 }//end getTokens()
Chris@17 626
Chris@17 627
Chris@17 628 /**
Chris@17 629 * Remove vars stored in this file that are no longer required.
Chris@17 630 *
Chris@17 631 * @return void
Chris@17 632 */
Chris@17 633 public function cleanUp()
Chris@17 634 {
Chris@17 635 $this->listenerTimes = null;
Chris@17 636 $this->content = null;
Chris@17 637 $this->tokens = null;
Chris@17 638 $this->metricTokens = null;
Chris@17 639 $this->tokenizer = null;
Chris@17 640 $this->fixer = null;
Chris@17 641 $this->config = null;
Chris@17 642 $this->ruleset = null;
Chris@17 643
Chris@17 644 }//end cleanUp()
Chris@17 645
Chris@17 646
Chris@17 647 /**
Chris@17 648 * Records an error against a specific token in the file.
Chris@17 649 *
Chris@17 650 * @param string $error The error message.
Chris@17 651 * @param int $stackPtr The stack position where the error occurred.
Chris@17 652 * @param string $code A violation code unique to the sniff message.
Chris@17 653 * @param array $data Replacements for the error message.
Chris@17 654 * @param int $severity The severity level for this error. A value of 0
Chris@17 655 * will be converted into the default severity level.
Chris@17 656 * @param boolean $fixable Can the error be fixed by the sniff?
Chris@17 657 *
Chris@17 658 * @return boolean
Chris@17 659 */
Chris@17 660 public function addError(
Chris@17 661 $error,
Chris@17 662 $stackPtr,
Chris@17 663 $code,
Chris@17 664 $data=[],
Chris@17 665 $severity=0,
Chris@17 666 $fixable=false
Chris@17 667 ) {
Chris@17 668 if ($stackPtr === null) {
Chris@17 669 $line = 1;
Chris@17 670 $column = 1;
Chris@17 671 } else {
Chris@17 672 $line = $this->tokens[$stackPtr]['line'];
Chris@17 673 $column = $this->tokens[$stackPtr]['column'];
Chris@17 674 }
Chris@17 675
Chris@17 676 return $this->addMessage(true, $error, $line, $column, $code, $data, $severity, $fixable);
Chris@17 677
Chris@17 678 }//end addError()
Chris@17 679
Chris@17 680
Chris@17 681 /**
Chris@17 682 * Records a warning against a specific token in the file.
Chris@17 683 *
Chris@17 684 * @param string $warning The error message.
Chris@17 685 * @param int $stackPtr The stack position where the error occurred.
Chris@17 686 * @param string $code A violation code unique to the sniff message.
Chris@17 687 * @param array $data Replacements for the warning message.
Chris@17 688 * @param int $severity The severity level for this warning. A value of 0
Chris@17 689 * will be converted into the default severity level.
Chris@17 690 * @param boolean $fixable Can the warning be fixed by the sniff?
Chris@17 691 *
Chris@17 692 * @return boolean
Chris@17 693 */
Chris@17 694 public function addWarning(
Chris@17 695 $warning,
Chris@17 696 $stackPtr,
Chris@17 697 $code,
Chris@17 698 $data=[],
Chris@17 699 $severity=0,
Chris@17 700 $fixable=false
Chris@17 701 ) {
Chris@17 702 if ($stackPtr === null) {
Chris@17 703 $line = 1;
Chris@17 704 $column = 1;
Chris@17 705 } else {
Chris@17 706 $line = $this->tokens[$stackPtr]['line'];
Chris@17 707 $column = $this->tokens[$stackPtr]['column'];
Chris@17 708 }
Chris@17 709
Chris@17 710 return $this->addMessage(false, $warning, $line, $column, $code, $data, $severity, $fixable);
Chris@17 711
Chris@17 712 }//end addWarning()
Chris@17 713
Chris@17 714
Chris@17 715 /**
Chris@17 716 * Records an error against a specific line in the file.
Chris@17 717 *
Chris@17 718 * @param string $error The error message.
Chris@17 719 * @param int $line The line on which the error occurred.
Chris@17 720 * @param string $code A violation code unique to the sniff message.
Chris@17 721 * @param array $data Replacements for the error message.
Chris@17 722 * @param int $severity The severity level for this error. A value of 0
Chris@17 723 * will be converted into the default severity level.
Chris@17 724 *
Chris@17 725 * @return boolean
Chris@17 726 */
Chris@17 727 public function addErrorOnLine(
Chris@17 728 $error,
Chris@17 729 $line,
Chris@17 730 $code,
Chris@17 731 $data=[],
Chris@17 732 $severity=0
Chris@17 733 ) {
Chris@17 734 return $this->addMessage(true, $error, $line, 1, $code, $data, $severity, false);
Chris@17 735
Chris@17 736 }//end addErrorOnLine()
Chris@17 737
Chris@17 738
Chris@17 739 /**
Chris@17 740 * Records a warning against a specific token in the file.
Chris@17 741 *
Chris@17 742 * @param string $warning The error message.
Chris@17 743 * @param int $line The line on which the warning occurred.
Chris@17 744 * @param string $code A violation code unique to the sniff message.
Chris@17 745 * @param array $data Replacements for the warning message.
Chris@17 746 * @param int $severity The severity level for this warning. A value of 0 will
Chris@17 747 * will be converted into the default severity level.
Chris@17 748 *
Chris@17 749 * @return boolean
Chris@17 750 */
Chris@17 751 public function addWarningOnLine(
Chris@17 752 $warning,
Chris@17 753 $line,
Chris@17 754 $code,
Chris@17 755 $data=[],
Chris@17 756 $severity=0
Chris@17 757 ) {
Chris@17 758 return $this->addMessage(false, $warning, $line, 1, $code, $data, $severity, false);
Chris@17 759
Chris@17 760 }//end addWarningOnLine()
Chris@17 761
Chris@17 762
Chris@17 763 /**
Chris@17 764 * Records a fixable error against a specific token in the file.
Chris@17 765 *
Chris@17 766 * Returns true if the error was recorded and should be fixed.
Chris@17 767 *
Chris@17 768 * @param string $error The error message.
Chris@17 769 * @param int $stackPtr The stack position where the error occurred.
Chris@17 770 * @param string $code A violation code unique to the sniff message.
Chris@17 771 * @param array $data Replacements for the error message.
Chris@17 772 * @param int $severity The severity level for this error. A value of 0
Chris@17 773 * will be converted into the default severity level.
Chris@17 774 *
Chris@17 775 * @return boolean
Chris@17 776 */
Chris@17 777 public function addFixableError(
Chris@17 778 $error,
Chris@17 779 $stackPtr,
Chris@17 780 $code,
Chris@17 781 $data=[],
Chris@17 782 $severity=0
Chris@17 783 ) {
Chris@17 784 $recorded = $this->addError($error, $stackPtr, $code, $data, $severity, true);
Chris@17 785 if ($recorded === true && $this->fixer->enabled === true) {
Chris@17 786 return true;
Chris@17 787 }
Chris@17 788
Chris@17 789 return false;
Chris@17 790
Chris@17 791 }//end addFixableError()
Chris@17 792
Chris@17 793
Chris@17 794 /**
Chris@17 795 * Records a fixable warning against a specific token in the file.
Chris@17 796 *
Chris@17 797 * Returns true if the warning was recorded and should be fixed.
Chris@17 798 *
Chris@17 799 * @param string $warning The error message.
Chris@17 800 * @param int $stackPtr The stack position where the error occurred.
Chris@17 801 * @param string $code A violation code unique to the sniff message.
Chris@17 802 * @param array $data Replacements for the warning message.
Chris@17 803 * @param int $severity The severity level for this warning. A value of 0
Chris@17 804 * will be converted into the default severity level.
Chris@17 805 *
Chris@17 806 * @return boolean
Chris@17 807 */
Chris@17 808 public function addFixableWarning(
Chris@17 809 $warning,
Chris@17 810 $stackPtr,
Chris@17 811 $code,
Chris@17 812 $data=[],
Chris@17 813 $severity=0
Chris@17 814 ) {
Chris@17 815 $recorded = $this->addWarning($warning, $stackPtr, $code, $data, $severity, true);
Chris@17 816 if ($recorded === true && $this->fixer->enabled === true) {
Chris@17 817 return true;
Chris@17 818 }
Chris@17 819
Chris@17 820 return false;
Chris@17 821
Chris@17 822 }//end addFixableWarning()
Chris@17 823
Chris@17 824
Chris@17 825 /**
Chris@17 826 * Adds an error to the error stack.
Chris@17 827 *
Chris@17 828 * @param boolean $error Is this an error message?
Chris@17 829 * @param string $message The text of the message.
Chris@17 830 * @param int $line The line on which the message occurred.
Chris@17 831 * @param int $column The column at which the message occurred.
Chris@17 832 * @param string $code A violation code unique to the sniff message.
Chris@17 833 * @param array $data Replacements for the message.
Chris@17 834 * @param int $severity The severity level for this message. A value of 0
Chris@17 835 * will be converted into the default severity level.
Chris@17 836 * @param boolean $fixable Can the problem be fixed by the sniff?
Chris@17 837 *
Chris@17 838 * @return boolean
Chris@17 839 */
Chris@17 840 protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable)
Chris@17 841 {
Chris@17 842 // Check if this line is ignoring all message codes.
Chris@17 843 if (isset($this->tokenizer->ignoredLines[$line]['.all']) === true) {
Chris@17 844 return false;
Chris@17 845 }
Chris@17 846
Chris@17 847 // Work out which sniff generated the message.
Chris@17 848 $parts = explode('.', $code);
Chris@17 849 if ($parts[0] === 'Internal') {
Chris@17 850 // An internal message.
Chris@17 851 $listenerCode = Util\Common::getSniffCode($this->activeListener);
Chris@17 852 $sniffCode = $code;
Chris@17 853 $checkCodes = [$sniffCode];
Chris@17 854 } else {
Chris@17 855 if ($parts[0] !== $code) {
Chris@17 856 // The full message code has been passed in.
Chris@17 857 $sniffCode = $code;
Chris@17 858 $listenerCode = substr($sniffCode, 0, strrpos($sniffCode, '.'));
Chris@17 859 } else {
Chris@17 860 $listenerCode = Util\Common::getSniffCode($this->activeListener);
Chris@17 861 $sniffCode = $listenerCode.'.'.$code;
Chris@17 862 $parts = explode('.', $sniffCode);
Chris@17 863 }
Chris@17 864
Chris@17 865 $checkCodes = [
Chris@17 866 $sniffCode,
Chris@17 867 $parts[0].'.'.$parts[1].'.'.$parts[2],
Chris@17 868 $parts[0].'.'.$parts[1],
Chris@17 869 $parts[0],
Chris@17 870 ];
Chris@17 871 }//end if
Chris@17 872
Chris@17 873 if (isset($this->tokenizer->ignoredLines[$line]) === true) {
Chris@17 874 // Check if this line is ignoring this specific message.
Chris@17 875 $ignored = false;
Chris@17 876 foreach ($checkCodes as $checkCode) {
Chris@17 877 if (isset($this->tokenizer->ignoredLines[$line][$checkCode]) === true) {
Chris@17 878 $ignored = true;
Chris@17 879 break;
Chris@17 880 }
Chris@17 881 }
Chris@17 882
Chris@17 883 // If it is ignored, make sure it's not whitelisted.
Chris@17 884 if ($ignored === true
Chris@17 885 && isset($this->tokenizer->ignoredLines[$line]['.except']) === true
Chris@17 886 ) {
Chris@17 887 foreach ($checkCodes as $checkCode) {
Chris@17 888 if (isset($this->tokenizer->ignoredLines[$line]['.except'][$checkCode]) === true) {
Chris@17 889 $ignored = false;
Chris@17 890 break;
Chris@17 891 }
Chris@17 892 }
Chris@17 893 }
Chris@17 894
Chris@17 895 if ($ignored === true) {
Chris@17 896 return false;
Chris@17 897 }
Chris@17 898 }//end if
Chris@17 899
Chris@17 900 $includeAll = true;
Chris@17 901 if ($this->configCache['cache'] === false
Chris@17 902 || $this->configCache['recordErrors'] === false
Chris@17 903 ) {
Chris@17 904 $includeAll = false;
Chris@17 905 }
Chris@17 906
Chris@17 907 // Filter out any messages for sniffs that shouldn't have run
Chris@17 908 // due to the use of the --sniffs command line argument.
Chris@17 909 if ($includeAll === false
Chris@17 910 && ((empty($this->configCache['sniffs']) === false
Chris@17 911 && in_array(strtolower($listenerCode), $this->configCache['sniffs']) === false)
Chris@17 912 || (empty($this->configCache['exclude']) === false
Chris@17 913 && in_array(strtolower($listenerCode), $this->configCache['exclude']) === true))
Chris@17 914 ) {
Chris@17 915 return false;
Chris@17 916 }
Chris@17 917
Chris@17 918 // If we know this sniff code is being ignored for this file, return early.
Chris@17 919 foreach ($checkCodes as $checkCode) {
Chris@17 920 if (isset($this->ignoredCodes[$checkCode]) === true) {
Chris@17 921 return false;
Chris@17 922 }
Chris@17 923 }
Chris@17 924
Chris@17 925 $oppositeType = 'warning';
Chris@17 926 if ($error === false) {
Chris@17 927 $oppositeType = 'error';
Chris@17 928 }
Chris@17 929
Chris@17 930 foreach ($checkCodes as $checkCode) {
Chris@17 931 // Make sure this message type has not been set to the opposite message type.
Chris@17 932 if (isset($this->ruleset->ruleset[$checkCode]['type']) === true
Chris@17 933 && $this->ruleset->ruleset[$checkCode]['type'] === $oppositeType
Chris@17 934 ) {
Chris@17 935 $error = !$error;
Chris@17 936 break;
Chris@17 937 }
Chris@17 938 }
Chris@17 939
Chris@17 940 if ($error === true) {
Chris@17 941 $configSeverity = $this->configCache['errorSeverity'];
Chris@17 942 $messageCount = &$this->errorCount;
Chris@17 943 $messages = &$this->errors;
Chris@17 944 } else {
Chris@17 945 $configSeverity = $this->configCache['warningSeverity'];
Chris@17 946 $messageCount = &$this->warningCount;
Chris@17 947 $messages = &$this->warnings;
Chris@17 948 }
Chris@17 949
Chris@17 950 if ($includeAll === false && $configSeverity === 0) {
Chris@17 951 // Don't bother doing any processing as these messages are just going to
Chris@17 952 // be hidden in the reports anyway.
Chris@17 953 return false;
Chris@17 954 }
Chris@17 955
Chris@17 956 if ($severity === 0) {
Chris@17 957 $severity = 5;
Chris@17 958 }
Chris@17 959
Chris@17 960 foreach ($checkCodes as $checkCode) {
Chris@17 961 // Make sure we are interested in this severity level.
Chris@17 962 if (isset($this->ruleset->ruleset[$checkCode]['severity']) === true) {
Chris@17 963 $severity = $this->ruleset->ruleset[$checkCode]['severity'];
Chris@17 964 break;
Chris@17 965 }
Chris@17 966 }
Chris@17 967
Chris@17 968 if ($includeAll === false && $configSeverity > $severity) {
Chris@17 969 return false;
Chris@17 970 }
Chris@17 971
Chris@17 972 // Make sure we are not ignoring this file.
Chris@17 973 $included = null;
Chris@17 974 foreach ($checkCodes as $checkCode) {
Chris@17 975 $patterns = null;
Chris@17 976
Chris@17 977 if (isset($this->configCache['includePatterns'][$checkCode]) === true) {
Chris@17 978 $patterns = $this->configCache['includePatterns'][$checkCode];
Chris@17 979 $excluding = false;
Chris@17 980 } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) {
Chris@17 981 $patterns = $this->configCache['ignorePatterns'][$checkCode];
Chris@17 982 $excluding = true;
Chris@17 983 }
Chris@17 984
Chris@17 985 if ($patterns === null) {
Chris@17 986 continue;
Chris@17 987 }
Chris@17 988
Chris@17 989 foreach ($patterns as $pattern => $type) {
Chris@17 990 // While there is support for a type of each pattern
Chris@17 991 // (absolute or relative) we don't actually support it here.
Chris@17 992 $replacements = [
Chris@17 993 '\\,' => ',',
Chris@17 994 '*' => '.*',
Chris@17 995 ];
Chris@17 996
Chris@17 997 // We assume a / directory separator, as do the exclude rules
Chris@17 998 // most developers write, so we need a special case for any system
Chris@17 999 // that is different.
Chris@17 1000 if (DIRECTORY_SEPARATOR === '\\') {
Chris@17 1001 $replacements['/'] = '\\\\';
Chris@17 1002 }
Chris@17 1003
Chris@17 1004 $pattern = '`'.strtr($pattern, $replacements).'`i';
Chris@17 1005 $matched = preg_match($pattern, $this->path);
Chris@17 1006
Chris@17 1007 if ($matched === 0) {
Chris@17 1008 if ($excluding === false && $included === null) {
Chris@17 1009 // This file path is not being included.
Chris@17 1010 $included = false;
Chris@17 1011 }
Chris@17 1012
Chris@17 1013 continue;
Chris@17 1014 }
Chris@17 1015
Chris@17 1016 if ($excluding === true) {
Chris@17 1017 // This file path is being excluded.
Chris@17 1018 $this->ignoredCodes[$checkCode] = true;
Chris@17 1019 return false;
Chris@17 1020 }
Chris@17 1021
Chris@17 1022 // This file path is being included.
Chris@17 1023 $included = true;
Chris@17 1024 break;
Chris@17 1025 }//end foreach
Chris@17 1026 }//end foreach
Chris@17 1027
Chris@17 1028 if ($included === false) {
Chris@17 1029 // There were include rules set, but this file
Chris@17 1030 // path didn't match any of them.
Chris@17 1031 return false;
Chris@17 1032 }
Chris@17 1033
Chris@17 1034 $messageCount++;
Chris@17 1035 if ($fixable === true) {
Chris@17 1036 $this->fixableCount++;
Chris@17 1037 }
Chris@17 1038
Chris@17 1039 if ($this->configCache['recordErrors'] === false
Chris@17 1040 && $includeAll === false
Chris@17 1041 ) {
Chris@17 1042 return true;
Chris@17 1043 }
Chris@17 1044
Chris@17 1045 // Work out the error message.
Chris@17 1046 if (isset($this->ruleset->ruleset[$sniffCode]['message']) === true) {
Chris@17 1047 $message = $this->ruleset->ruleset[$sniffCode]['message'];
Chris@17 1048 }
Chris@17 1049
Chris@17 1050 if (empty($data) === false) {
Chris@17 1051 $message = vsprintf($message, $data);
Chris@17 1052 }
Chris@17 1053
Chris@17 1054 if (isset($messages[$line]) === false) {
Chris@17 1055 $messages[$line] = [];
Chris@17 1056 }
Chris@17 1057
Chris@17 1058 if (isset($messages[$line][$column]) === false) {
Chris@17 1059 $messages[$line][$column] = [];
Chris@17 1060 }
Chris@17 1061
Chris@17 1062 $messages[$line][$column][] = [
Chris@17 1063 'message' => $message,
Chris@17 1064 'source' => $sniffCode,
Chris@17 1065 'listener' => $this->activeListener,
Chris@17 1066 'severity' => $severity,
Chris@17 1067 'fixable' => $fixable,
Chris@17 1068 ];
Chris@17 1069
Chris@17 1070 if (PHP_CODESNIFFER_VERBOSITY > 1
Chris@17 1071 && $this->fixer->enabled === true
Chris@17 1072 && $fixable === true
Chris@17 1073 ) {
Chris@17 1074 @ob_end_clean();
Chris@17 1075 echo "\tE: [Line $line] $message ($sniffCode)".PHP_EOL;
Chris@17 1076 ob_start();
Chris@17 1077 }
Chris@17 1078
Chris@17 1079 return true;
Chris@17 1080
Chris@17 1081 }//end addMessage()
Chris@17 1082
Chris@17 1083
Chris@17 1084 /**
Chris@17 1085 * Record a metric about the file being examined.
Chris@17 1086 *
Chris@17 1087 * @param int $stackPtr The stack position where the metric was recorded.
Chris@17 1088 * @param string $metric The name of the metric being recorded.
Chris@17 1089 * @param string $value The value of the metric being recorded.
Chris@17 1090 *
Chris@17 1091 * @return boolean
Chris@17 1092 */
Chris@17 1093 public function recordMetric($stackPtr, $metric, $value)
Chris@17 1094 {
Chris@17 1095 if (isset($this->metrics[$metric]) === false) {
Chris@17 1096 $this->metrics[$metric] = ['values' => [$value => 1]];
Chris@17 1097 $this->metricTokens[$metric][$stackPtr] = true;
Chris@17 1098 } else if (isset($this->metricTokens[$metric][$stackPtr]) === false) {
Chris@17 1099 $this->metricTokens[$metric][$stackPtr] = true;
Chris@17 1100 if (isset($this->metrics[$metric]['values'][$value]) === false) {
Chris@17 1101 $this->metrics[$metric]['values'][$value] = 1;
Chris@17 1102 } else {
Chris@17 1103 $this->metrics[$metric]['values'][$value]++;
Chris@17 1104 }
Chris@17 1105 }
Chris@17 1106
Chris@17 1107 return true;
Chris@17 1108
Chris@17 1109 }//end recordMetric()
Chris@17 1110
Chris@17 1111
Chris@17 1112 /**
Chris@17 1113 * Returns the number of errors raised.
Chris@17 1114 *
Chris@17 1115 * @return int
Chris@17 1116 */
Chris@17 1117 public function getErrorCount()
Chris@17 1118 {
Chris@17 1119 return $this->errorCount;
Chris@17 1120
Chris@17 1121 }//end getErrorCount()
Chris@17 1122
Chris@17 1123
Chris@17 1124 /**
Chris@17 1125 * Returns the number of warnings raised.
Chris@17 1126 *
Chris@17 1127 * @return int
Chris@17 1128 */
Chris@17 1129 public function getWarningCount()
Chris@17 1130 {
Chris@17 1131 return $this->warningCount;
Chris@17 1132
Chris@17 1133 }//end getWarningCount()
Chris@17 1134
Chris@17 1135
Chris@17 1136 /**
Chris@17 1137 * Returns the number of successes recorded.
Chris@17 1138 *
Chris@17 1139 * @return int
Chris@17 1140 */
Chris@17 1141 public function getSuccessCount()
Chris@17 1142 {
Chris@17 1143 return $this->successCount;
Chris@17 1144
Chris@17 1145 }//end getSuccessCount()
Chris@17 1146
Chris@17 1147
Chris@17 1148 /**
Chris@17 1149 * Returns the number of fixable errors/warnings raised.
Chris@17 1150 *
Chris@17 1151 * @return int
Chris@17 1152 */
Chris@17 1153 public function getFixableCount()
Chris@17 1154 {
Chris@17 1155 return $this->fixableCount;
Chris@17 1156
Chris@17 1157 }//end getFixableCount()
Chris@17 1158
Chris@17 1159
Chris@17 1160 /**
Chris@17 1161 * Returns the number of fixed errors/warnings.
Chris@17 1162 *
Chris@17 1163 * @return int
Chris@17 1164 */
Chris@17 1165 public function getFixedCount()
Chris@17 1166 {
Chris@17 1167 return $this->fixedCount;
Chris@17 1168
Chris@17 1169 }//end getFixedCount()
Chris@17 1170
Chris@17 1171
Chris@17 1172 /**
Chris@17 1173 * Returns the list of ignored lines.
Chris@17 1174 *
Chris@17 1175 * @return array
Chris@17 1176 */
Chris@17 1177 public function getIgnoredLines()
Chris@17 1178 {
Chris@17 1179 return $this->tokenizer->ignoredLines;
Chris@17 1180
Chris@17 1181 }//end getIgnoredLines()
Chris@17 1182
Chris@17 1183
Chris@17 1184 /**
Chris@17 1185 * Returns the errors raised from processing this file.
Chris@17 1186 *
Chris@17 1187 * @return array
Chris@17 1188 */
Chris@17 1189 public function getErrors()
Chris@17 1190 {
Chris@17 1191 return $this->errors;
Chris@17 1192
Chris@17 1193 }//end getErrors()
Chris@17 1194
Chris@17 1195
Chris@17 1196 /**
Chris@17 1197 * Returns the warnings raised from processing this file.
Chris@17 1198 *
Chris@17 1199 * @return array
Chris@17 1200 */
Chris@17 1201 public function getWarnings()
Chris@17 1202 {
Chris@17 1203 return $this->warnings;
Chris@17 1204
Chris@17 1205 }//end getWarnings()
Chris@17 1206
Chris@17 1207
Chris@17 1208 /**
Chris@17 1209 * Returns the metrics found while processing this file.
Chris@17 1210 *
Chris@17 1211 * @return array
Chris@17 1212 */
Chris@17 1213 public function getMetrics()
Chris@17 1214 {
Chris@17 1215 return $this->metrics;
Chris@17 1216
Chris@17 1217 }//end getMetrics()
Chris@17 1218
Chris@17 1219
Chris@17 1220 /**
Chris@17 1221 * Returns the absolute filename of this file.
Chris@17 1222 *
Chris@17 1223 * @return string
Chris@17 1224 */
Chris@17 1225 public function getFilename()
Chris@17 1226 {
Chris@17 1227 return $this->path;
Chris@17 1228
Chris@17 1229 }//end getFilename()
Chris@17 1230
Chris@17 1231
Chris@17 1232 /**
Chris@17 1233 * Returns the declaration names for classes, interfaces, traits, and functions.
Chris@17 1234 *
Chris@17 1235 * @param int $stackPtr The position of the declaration token which
Chris@17 1236 * declared the class, interface, trait, or function.
Chris@17 1237 *
Chris@17 1238 * @return string|null The name of the class, interface, trait, or function;
Chris@17 1239 * or NULL if the function or class is anonymous.
Chris@17 1240 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
Chris@17 1241 * T_FUNCTION, T_CLASS, T_ANON_CLASS,
Chris@17 1242 * T_CLOSURE, T_TRAIT, or T_INTERFACE.
Chris@17 1243 */
Chris@17 1244 public function getDeclarationName($stackPtr)
Chris@17 1245 {
Chris@17 1246 $tokenCode = $this->tokens[$stackPtr]['code'];
Chris@17 1247
Chris@17 1248 if ($tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) {
Chris@17 1249 return null;
Chris@17 1250 }
Chris@17 1251
Chris@17 1252 if ($tokenCode !== T_FUNCTION
Chris@17 1253 && $tokenCode !== T_CLASS
Chris@17 1254 && $tokenCode !== T_INTERFACE
Chris@17 1255 && $tokenCode !== T_TRAIT
Chris@17 1256 ) {
Chris@17 1257 throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
Chris@17 1258 }
Chris@17 1259
Chris@17 1260 if ($tokenCode === T_FUNCTION
Chris@17 1261 && strtolower($this->tokens[$stackPtr]['content']) !== 'function'
Chris@17 1262 ) {
Chris@17 1263 // This is a function declared without the "function" keyword.
Chris@17 1264 // So this token is the function name.
Chris@17 1265 return $this->tokens[$stackPtr]['content'];
Chris@17 1266 }
Chris@17 1267
Chris@17 1268 $content = null;
Chris@17 1269 for ($i = $stackPtr; $i < $this->numTokens; $i++) {
Chris@17 1270 if ($this->tokens[$i]['code'] === T_STRING) {
Chris@17 1271 $content = $this->tokens[$i]['content'];
Chris@17 1272 break;
Chris@17 1273 }
Chris@17 1274 }
Chris@17 1275
Chris@17 1276 return $content;
Chris@17 1277
Chris@17 1278 }//end getDeclarationName()
Chris@17 1279
Chris@17 1280
Chris@17 1281 /**
Chris@17 1282 * Returns the method parameters for the specified function token.
Chris@17 1283 *
Chris@17 1284 * Each parameter is in the following format:
Chris@17 1285 *
Chris@17 1286 * <code>
Chris@17 1287 * 0 => array(
Chris@17 1288 * 'name' => '$var', // The variable name.
Chris@17 1289 * 'token' => integer, // The stack pointer to the variable name.
Chris@17 1290 * 'content' => string, // The full content of the variable definition.
Chris@17 1291 * 'pass_by_reference' => boolean, // Is the variable passed by reference?
Chris@17 1292 * 'variable_length' => boolean, // Is the param of variable length through use of `...` ?
Chris@17 1293 * 'type_hint' => string, // The type hint for the variable.
Chris@17 1294 * 'type_hint_token' => integer, // The stack pointer to the type hint
Chris@17 1295 * // or false if there is no type hint.
Chris@17 1296 * 'nullable_type' => boolean, // Is the variable using a nullable type?
Chris@17 1297 * )
Chris@17 1298 * </code>
Chris@17 1299 *
Chris@17 1300 * Parameters with default values have an additional array index of
Chris@17 1301 * 'default' with the value of the default as a string.
Chris@17 1302 *
Chris@17 1303 * @param int $stackPtr The position in the stack of the function token
Chris@17 1304 * to acquire the parameters for.
Chris@17 1305 *
Chris@17 1306 * @return array
Chris@17 1307 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified $stackPtr is not of
Chris@17 1308 * type T_FUNCTION or T_CLOSURE.
Chris@17 1309 */
Chris@17 1310 public function getMethodParameters($stackPtr)
Chris@17 1311 {
Chris@17 1312 if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
Chris@17 1313 && $this->tokens[$stackPtr]['code'] !== T_CLOSURE
Chris@17 1314 ) {
Chris@17 1315 throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
Chris@17 1316 }
Chris@17 1317
Chris@17 1318 $opener = $this->tokens[$stackPtr]['parenthesis_opener'];
Chris@17 1319 $closer = $this->tokens[$stackPtr]['parenthesis_closer'];
Chris@17 1320
Chris@17 1321 $vars = [];
Chris@17 1322 $currVar = null;
Chris@17 1323 $paramStart = ($opener + 1);
Chris@17 1324 $defaultStart = null;
Chris@17 1325 $paramCount = 0;
Chris@17 1326 $passByReference = false;
Chris@17 1327 $variableLength = false;
Chris@17 1328 $typeHint = '';
Chris@17 1329 $typeHintToken = false;
Chris@17 1330 $nullableType = false;
Chris@17 1331
Chris@17 1332 for ($i = $paramStart; $i <= $closer; $i++) {
Chris@17 1333 // Check to see if this token has a parenthesis or bracket opener. If it does
Chris@17 1334 // it's likely to be an array which might have arguments in it. This
Chris@17 1335 // could cause problems in our parsing below, so lets just skip to the
Chris@17 1336 // end of it.
Chris@17 1337 if (isset($this->tokens[$i]['parenthesis_opener']) === true) {
Chris@17 1338 // Don't do this if it's the close parenthesis for the method.
Chris@17 1339 if ($i !== $this->tokens[$i]['parenthesis_closer']) {
Chris@17 1340 $i = ($this->tokens[$i]['parenthesis_closer'] + 1);
Chris@17 1341 }
Chris@17 1342 }
Chris@17 1343
Chris@17 1344 if (isset($this->tokens[$i]['bracket_opener']) === true) {
Chris@17 1345 // Don't do this if it's the close parenthesis for the method.
Chris@17 1346 if ($i !== $this->tokens[$i]['bracket_closer']) {
Chris@17 1347 $i = ($this->tokens[$i]['bracket_closer'] + 1);
Chris@17 1348 }
Chris@17 1349 }
Chris@17 1350
Chris@17 1351 switch ($this->tokens[$i]['code']) {
Chris@17 1352 case T_BITWISE_AND:
Chris@17 1353 if ($defaultStart === null) {
Chris@17 1354 $passByReference = true;
Chris@17 1355 }
Chris@17 1356 break;
Chris@17 1357 case T_VARIABLE:
Chris@17 1358 $currVar = $i;
Chris@17 1359 break;
Chris@17 1360 case T_ELLIPSIS:
Chris@17 1361 $variableLength = true;
Chris@17 1362 break;
Chris@17 1363 case T_CALLABLE:
Chris@17 1364 if ($typeHintToken === false) {
Chris@17 1365 $typeHintToken = $i;
Chris@17 1366 }
Chris@17 1367
Chris@17 1368 $typeHint .= $this->tokens[$i]['content'];
Chris@17 1369 break;
Chris@17 1370 case T_SELF:
Chris@17 1371 case T_PARENT:
Chris@17 1372 case T_STATIC:
Chris@17 1373 // Self and parent are valid, static invalid, but was probably intended as type hint.
Chris@17 1374 if (isset($defaultStart) === false) {
Chris@17 1375 if ($typeHintToken === false) {
Chris@17 1376 $typeHintToken = $i;
Chris@17 1377 }
Chris@17 1378
Chris@17 1379 $typeHint .= $this->tokens[$i]['content'];
Chris@17 1380 }
Chris@17 1381 break;
Chris@17 1382 case T_STRING:
Chris@17 1383 // This is a string, so it may be a type hint, but it could
Chris@17 1384 // also be a constant used as a default value.
Chris@17 1385 $prevComma = false;
Chris@17 1386 for ($t = $i; $t >= $opener; $t--) {
Chris@17 1387 if ($this->tokens[$t]['code'] === T_COMMA) {
Chris@17 1388 $prevComma = $t;
Chris@17 1389 break;
Chris@17 1390 }
Chris@17 1391 }
Chris@17 1392
Chris@17 1393 if ($prevComma !== false) {
Chris@17 1394 $nextEquals = false;
Chris@17 1395 for ($t = $prevComma; $t < $i; $t++) {
Chris@17 1396 if ($this->tokens[$t]['code'] === T_EQUAL) {
Chris@17 1397 $nextEquals = $t;
Chris@17 1398 break;
Chris@17 1399 }
Chris@17 1400 }
Chris@17 1401
Chris@17 1402 if ($nextEquals !== false) {
Chris@17 1403 break;
Chris@17 1404 }
Chris@17 1405 }
Chris@17 1406
Chris@17 1407 if ($defaultStart === null) {
Chris@17 1408 if ($typeHintToken === false) {
Chris@17 1409 $typeHintToken = $i;
Chris@17 1410 }
Chris@17 1411
Chris@17 1412 $typeHint .= $this->tokens[$i]['content'];
Chris@17 1413 }
Chris@17 1414 break;
Chris@17 1415 case T_NS_SEPARATOR:
Chris@17 1416 // Part of a type hint or default value.
Chris@17 1417 if ($defaultStart === null) {
Chris@17 1418 if ($typeHintToken === false) {
Chris@17 1419 $typeHintToken = $i;
Chris@17 1420 }
Chris@17 1421
Chris@17 1422 $typeHint .= $this->tokens[$i]['content'];
Chris@17 1423 }
Chris@17 1424 break;
Chris@17 1425 case T_NULLABLE:
Chris@17 1426 if ($defaultStart === null) {
Chris@17 1427 $nullableType = true;
Chris@17 1428 $typeHint .= $this->tokens[$i]['content'];
Chris@17 1429 }
Chris@17 1430 break;
Chris@17 1431 case T_CLOSE_PARENTHESIS:
Chris@17 1432 case T_COMMA:
Chris@17 1433 // If it's null, then there must be no parameters for this
Chris@17 1434 // method.
Chris@17 1435 if ($currVar === null) {
Chris@17 1436 continue 2;
Chris@17 1437 }
Chris@17 1438
Chris@17 1439 $vars[$paramCount] = [];
Chris@17 1440 $vars[$paramCount]['token'] = $currVar;
Chris@17 1441 $vars[$paramCount]['name'] = $this->tokens[$currVar]['content'];
Chris@17 1442 $vars[$paramCount]['content'] = trim($this->getTokensAsString($paramStart, ($i - $paramStart)));
Chris@17 1443
Chris@17 1444 if ($defaultStart !== null) {
Chris@17 1445 $vars[$paramCount]['default'] = trim($this->getTokensAsString($defaultStart, ($i - $defaultStart)));
Chris@17 1446 }
Chris@17 1447
Chris@17 1448 $vars[$paramCount]['pass_by_reference'] = $passByReference;
Chris@17 1449 $vars[$paramCount]['variable_length'] = $variableLength;
Chris@17 1450 $vars[$paramCount]['type_hint'] = $typeHint;
Chris@17 1451 $vars[$paramCount]['type_hint_token'] = $typeHintToken;
Chris@17 1452 $vars[$paramCount]['nullable_type'] = $nullableType;
Chris@17 1453
Chris@17 1454 // Reset the vars, as we are about to process the next parameter.
Chris@17 1455 $defaultStart = null;
Chris@17 1456 $paramStart = ($i + 1);
Chris@17 1457 $passByReference = false;
Chris@17 1458 $variableLength = false;
Chris@17 1459 $typeHint = '';
Chris@17 1460 $typeHintToken = false;
Chris@17 1461 $nullableType = false;
Chris@17 1462
Chris@17 1463 $paramCount++;
Chris@17 1464 break;
Chris@17 1465 case T_EQUAL:
Chris@17 1466 $defaultStart = ($i + 1);
Chris@17 1467 break;
Chris@17 1468 }//end switch
Chris@17 1469 }//end for
Chris@17 1470
Chris@17 1471 return $vars;
Chris@17 1472
Chris@17 1473 }//end getMethodParameters()
Chris@17 1474
Chris@17 1475
Chris@17 1476 /**
Chris@17 1477 * Returns the visibility and implementation properties of a method.
Chris@17 1478 *
Chris@17 1479 * The format of the array is:
Chris@17 1480 * <code>
Chris@17 1481 * array(
Chris@17 1482 * 'scope' => 'public', // public protected or protected
Chris@17 1483 * 'scope_specified' => true, // true is scope keyword was found.
Chris@17 1484 * 'return_type' => '', // the return type of the method.
Chris@17 1485 * 'return_type_token' => integer, // The stack pointer to the start of the return type
Chris@17 1486 * // or false if there is no return type.
Chris@17 1487 * 'nullable_return_type' => false, // true if the return type is nullable.
Chris@17 1488 * 'is_abstract' => false, // true if the abstract keyword was found.
Chris@17 1489 * 'is_final' => false, // true if the final keyword was found.
Chris@17 1490 * 'is_static' => false, // true if the static keyword was found.
Chris@17 1491 * 'has_body' => false, // true if the method has a body
Chris@17 1492 * );
Chris@17 1493 * </code>
Chris@17 1494 *
Chris@17 1495 * @param int $stackPtr The position in the stack of the function token to
Chris@17 1496 * acquire the properties for.
Chris@17 1497 *
Chris@17 1498 * @return array
Chris@17 1499 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
Chris@17 1500 * T_FUNCTION token.
Chris@17 1501 */
Chris@17 1502 public function getMethodProperties($stackPtr)
Chris@17 1503 {
Chris@17 1504 if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
Chris@17 1505 && $this->tokens[$stackPtr]['code'] !== T_CLOSURE
Chris@17 1506 ) {
Chris@17 1507 throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
Chris@17 1508 }
Chris@17 1509
Chris@17 1510 if ($this->tokens[$stackPtr]['code'] === T_FUNCTION) {
Chris@17 1511 $valid = [
Chris@17 1512 T_PUBLIC => T_PUBLIC,
Chris@17 1513 T_PRIVATE => T_PRIVATE,
Chris@17 1514 T_PROTECTED => T_PROTECTED,
Chris@17 1515 T_STATIC => T_STATIC,
Chris@17 1516 T_FINAL => T_FINAL,
Chris@17 1517 T_ABSTRACT => T_ABSTRACT,
Chris@17 1518 T_WHITESPACE => T_WHITESPACE,
Chris@17 1519 T_COMMENT => T_COMMENT,
Chris@17 1520 T_DOC_COMMENT => T_DOC_COMMENT,
Chris@17 1521 ];
Chris@17 1522 } else {
Chris@17 1523 $valid = [
Chris@17 1524 T_STATIC => T_STATIC,
Chris@17 1525 T_WHITESPACE => T_WHITESPACE,
Chris@17 1526 T_COMMENT => T_COMMENT,
Chris@17 1527 T_DOC_COMMENT => T_DOC_COMMENT,
Chris@17 1528 ];
Chris@17 1529 }
Chris@17 1530
Chris@17 1531 $scope = 'public';
Chris@17 1532 $scopeSpecified = false;
Chris@17 1533 $isAbstract = false;
Chris@17 1534 $isFinal = false;
Chris@17 1535 $isStatic = false;
Chris@17 1536
Chris@17 1537 for ($i = ($stackPtr - 1); $i > 0; $i--) {
Chris@17 1538 if (isset($valid[$this->tokens[$i]['code']]) === false) {
Chris@17 1539 break;
Chris@17 1540 }
Chris@17 1541
Chris@17 1542 switch ($this->tokens[$i]['code']) {
Chris@17 1543 case T_PUBLIC:
Chris@17 1544 $scope = 'public';
Chris@17 1545 $scopeSpecified = true;
Chris@17 1546 break;
Chris@17 1547 case T_PRIVATE:
Chris@17 1548 $scope = 'private';
Chris@17 1549 $scopeSpecified = true;
Chris@17 1550 break;
Chris@17 1551 case T_PROTECTED:
Chris@17 1552 $scope = 'protected';
Chris@17 1553 $scopeSpecified = true;
Chris@17 1554 break;
Chris@17 1555 case T_ABSTRACT:
Chris@17 1556 $isAbstract = true;
Chris@17 1557 break;
Chris@17 1558 case T_FINAL:
Chris@17 1559 $isFinal = true;
Chris@17 1560 break;
Chris@17 1561 case T_STATIC:
Chris@17 1562 $isStatic = true;
Chris@17 1563 break;
Chris@17 1564 }//end switch
Chris@17 1565 }//end for
Chris@17 1566
Chris@17 1567 $returnType = '';
Chris@17 1568 $returnTypeToken = false;
Chris@17 1569 $nullableReturnType = false;
Chris@17 1570 $hasBody = true;
Chris@17 1571
Chris@17 1572 if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true) {
Chris@17 1573 $scopeOpener = null;
Chris@17 1574 if (isset($this->tokens[$stackPtr]['scope_opener']) === true) {
Chris@17 1575 $scopeOpener = $this->tokens[$stackPtr]['scope_opener'];
Chris@17 1576 }
Chris@17 1577
Chris@17 1578 $valid = [
Chris@17 1579 T_STRING => T_STRING,
Chris@17 1580 T_CALLABLE => T_CALLABLE,
Chris@17 1581 T_SELF => T_SELF,
Chris@17 1582 T_PARENT => T_PARENT,
Chris@17 1583 T_NS_SEPARATOR => T_NS_SEPARATOR,
Chris@17 1584 ];
Chris@17 1585
Chris@17 1586 for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) {
Chris@17 1587 if (($scopeOpener === null && $this->tokens[$i]['code'] === T_SEMICOLON)
Chris@17 1588 || ($scopeOpener !== null && $i === $scopeOpener)
Chris@17 1589 ) {
Chris@17 1590 // End of function definition.
Chris@17 1591 break;
Chris@17 1592 }
Chris@17 1593
Chris@17 1594 if ($this->tokens[$i]['code'] === T_NULLABLE) {
Chris@17 1595 $nullableReturnType = true;
Chris@17 1596 }
Chris@17 1597
Chris@17 1598 if (isset($valid[$this->tokens[$i]['code']]) === true) {
Chris@17 1599 if ($returnTypeToken === false) {
Chris@17 1600 $returnTypeToken = $i;
Chris@17 1601 }
Chris@17 1602
Chris@17 1603 $returnType .= $this->tokens[$i]['content'];
Chris@17 1604 }
Chris@17 1605 }
Chris@17 1606
Chris@17 1607 $end = $this->findNext([T_OPEN_CURLY_BRACKET, T_SEMICOLON], $this->tokens[$stackPtr]['parenthesis_closer']);
Chris@17 1608 $hasBody = $this->tokens[$end]['code'] === T_OPEN_CURLY_BRACKET;
Chris@17 1609 }//end if
Chris@17 1610
Chris@17 1611 if ($returnType !== '' && $nullableReturnType === true) {
Chris@17 1612 $returnType = '?'.$returnType;
Chris@17 1613 }
Chris@17 1614
Chris@17 1615 return [
Chris@17 1616 'scope' => $scope,
Chris@17 1617 'scope_specified' => $scopeSpecified,
Chris@17 1618 'return_type' => $returnType,
Chris@17 1619 'return_type_token' => $returnTypeToken,
Chris@17 1620 'nullable_return_type' => $nullableReturnType,
Chris@17 1621 'is_abstract' => $isAbstract,
Chris@17 1622 'is_final' => $isFinal,
Chris@17 1623 'is_static' => $isStatic,
Chris@17 1624 'has_body' => $hasBody,
Chris@17 1625 ];
Chris@17 1626
Chris@17 1627 }//end getMethodProperties()
Chris@17 1628
Chris@17 1629
Chris@17 1630 /**
Chris@17 1631 * Returns the visibility and implementation properties of the class member
Chris@17 1632 * variable found at the specified position in the stack.
Chris@17 1633 *
Chris@17 1634 * The format of the array is:
Chris@17 1635 *
Chris@17 1636 * <code>
Chris@17 1637 * array(
Chris@17 1638 * 'scope' => 'public', // public protected or protected.
Chris@17 1639 * 'scope_specified' => false, // true if the scope was explicitly specified.
Chris@17 1640 * 'is_static' => false, // true if the static keyword was found.
Chris@17 1641 * );
Chris@17 1642 * </code>
Chris@17 1643 *
Chris@17 1644 * @param int $stackPtr The position in the stack of the T_VARIABLE token to
Chris@17 1645 * acquire the properties for.
Chris@17 1646 *
Chris@17 1647 * @return array
Chris@17 1648 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
Chris@17 1649 * T_VARIABLE token, or if the position is not
Chris@17 1650 * a class member variable.
Chris@17 1651 */
Chris@17 1652 public function getMemberProperties($stackPtr)
Chris@17 1653 {
Chris@17 1654 if ($this->tokens[$stackPtr]['code'] !== T_VARIABLE) {
Chris@17 1655 throw new TokenizerException('$stackPtr must be of type T_VARIABLE');
Chris@17 1656 }
Chris@17 1657
Chris@17 1658 $conditions = array_keys($this->tokens[$stackPtr]['conditions']);
Chris@17 1659 $ptr = array_pop($conditions);
Chris@17 1660 if (isset($this->tokens[$ptr]) === false
Chris@17 1661 || ($this->tokens[$ptr]['code'] !== T_CLASS
Chris@17 1662 && $this->tokens[$ptr]['code'] !== T_ANON_CLASS
Chris@17 1663 && $this->tokens[$ptr]['code'] !== T_TRAIT)
Chris@17 1664 ) {
Chris@17 1665 if (isset($this->tokens[$ptr]) === true
Chris@17 1666 && $this->tokens[$ptr]['code'] === T_INTERFACE
Chris@17 1667 ) {
Chris@17 1668 // T_VARIABLEs in interfaces can actually be method arguments
Chris@17 1669 // but they wont be seen as being inside the method because there
Chris@17 1670 // are no scope openers and closers for abstract methods. If it is in
Chris@17 1671 // parentheses, we can be pretty sure it is a method argument.
Chris@17 1672 if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false
Chris@17 1673 || empty($this->tokens[$stackPtr]['nested_parenthesis']) === true
Chris@17 1674 ) {
Chris@17 1675 $error = 'Possible parse error: interfaces may not include member vars';
Chris@17 1676 $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
Chris@17 1677 return [];
Chris@17 1678 }
Chris@17 1679 } else {
Chris@17 1680 throw new TokenizerException('$stackPtr is not a class member var');
Chris@17 1681 }
Chris@17 1682 }
Chris@17 1683
Chris@17 1684 // Make sure it's not a method parameter.
Chris@17 1685 if (empty($this->tokens[$stackPtr]['nested_parenthesis']) === false) {
Chris@17 1686 $parenthesis = array_keys($this->tokens[$stackPtr]['nested_parenthesis']);
Chris@17 1687 $deepestOpen = array_pop($parenthesis);
Chris@17 1688 if ($deepestOpen > $ptr
Chris@17 1689 && isset($this->tokens[$deepestOpen]['parenthesis_owner']) === true
Chris@17 1690 && $this->tokens[$this->tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION
Chris@17 1691 ) {
Chris@17 1692 throw new TokenizerException('$stackPtr is not a class member var');
Chris@17 1693 }
Chris@17 1694 }
Chris@17 1695
Chris@17 1696 $valid = [
Chris@17 1697 T_PUBLIC => T_PUBLIC,
Chris@17 1698 T_PRIVATE => T_PRIVATE,
Chris@17 1699 T_PROTECTED => T_PROTECTED,
Chris@17 1700 T_STATIC => T_STATIC,
Chris@17 1701 T_VAR => T_VAR,
Chris@17 1702 ];
Chris@17 1703
Chris@17 1704 $valid += Util\Tokens::$emptyTokens;
Chris@17 1705
Chris@17 1706 $scope = 'public';
Chris@17 1707 $scopeSpecified = false;
Chris@17 1708 $isStatic = false;
Chris@17 1709
Chris@17 1710 $startOfStatement = $this->findPrevious(
Chris@17 1711 [
Chris@17 1712 T_SEMICOLON,
Chris@17 1713 T_OPEN_CURLY_BRACKET,
Chris@17 1714 T_CLOSE_CURLY_BRACKET,
Chris@17 1715 ],
Chris@17 1716 ($stackPtr - 1)
Chris@17 1717 );
Chris@17 1718
Chris@17 1719 for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) {
Chris@17 1720 if (isset($valid[$this->tokens[$i]['code']]) === false) {
Chris@17 1721 break;
Chris@17 1722 }
Chris@17 1723
Chris@17 1724 switch ($this->tokens[$i]['code']) {
Chris@17 1725 case T_PUBLIC:
Chris@17 1726 $scope = 'public';
Chris@17 1727 $scopeSpecified = true;
Chris@17 1728 break;
Chris@17 1729 case T_PRIVATE:
Chris@17 1730 $scope = 'private';
Chris@17 1731 $scopeSpecified = true;
Chris@17 1732 break;
Chris@17 1733 case T_PROTECTED:
Chris@17 1734 $scope = 'protected';
Chris@17 1735 $scopeSpecified = true;
Chris@17 1736 break;
Chris@17 1737 case T_STATIC:
Chris@17 1738 $isStatic = true;
Chris@17 1739 break;
Chris@17 1740 }
Chris@17 1741 }//end for
Chris@17 1742
Chris@17 1743 return [
Chris@17 1744 'scope' => $scope,
Chris@17 1745 'scope_specified' => $scopeSpecified,
Chris@17 1746 'is_static' => $isStatic,
Chris@17 1747 ];
Chris@17 1748
Chris@17 1749 }//end getMemberProperties()
Chris@17 1750
Chris@17 1751
Chris@17 1752 /**
Chris@17 1753 * Returns the visibility and implementation properties of a class.
Chris@17 1754 *
Chris@17 1755 * The format of the array is:
Chris@17 1756 * <code>
Chris@17 1757 * array(
Chris@17 1758 * 'is_abstract' => false, // true if the abstract keyword was found.
Chris@17 1759 * 'is_final' => false, // true if the final keyword was found.
Chris@17 1760 * );
Chris@17 1761 * </code>
Chris@17 1762 *
Chris@17 1763 * @param int $stackPtr The position in the stack of the T_CLASS token to
Chris@17 1764 * acquire the properties for.
Chris@17 1765 *
Chris@17 1766 * @return array
Chris@17 1767 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
Chris@17 1768 * T_CLASS token.
Chris@17 1769 */
Chris@17 1770 public function getClassProperties($stackPtr)
Chris@17 1771 {
Chris@17 1772 if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
Chris@17 1773 throw new TokenizerException('$stackPtr must be of type T_CLASS');
Chris@17 1774 }
Chris@17 1775
Chris@17 1776 $valid = [
Chris@17 1777 T_FINAL => T_FINAL,
Chris@17 1778 T_ABSTRACT => T_ABSTRACT,
Chris@17 1779 T_WHITESPACE => T_WHITESPACE,
Chris@17 1780 T_COMMENT => T_COMMENT,
Chris@17 1781 T_DOC_COMMENT => T_DOC_COMMENT,
Chris@17 1782 ];
Chris@17 1783
Chris@17 1784 $isAbstract = false;
Chris@17 1785 $isFinal = false;
Chris@17 1786
Chris@17 1787 for ($i = ($stackPtr - 1); $i > 0; $i--) {
Chris@17 1788 if (isset($valid[$this->tokens[$i]['code']]) === false) {
Chris@17 1789 break;
Chris@17 1790 }
Chris@17 1791
Chris@17 1792 switch ($this->tokens[$i]['code']) {
Chris@17 1793 case T_ABSTRACT:
Chris@17 1794 $isAbstract = true;
Chris@17 1795 break;
Chris@17 1796
Chris@17 1797 case T_FINAL:
Chris@17 1798 $isFinal = true;
Chris@17 1799 break;
Chris@17 1800 }
Chris@17 1801 }//end for
Chris@17 1802
Chris@17 1803 return [
Chris@17 1804 'is_abstract' => $isAbstract,
Chris@17 1805 'is_final' => $isFinal,
Chris@17 1806 ];
Chris@17 1807
Chris@17 1808 }//end getClassProperties()
Chris@17 1809
Chris@17 1810
Chris@17 1811 /**
Chris@17 1812 * Determine if the passed token is a reference operator.
Chris@17 1813 *
Chris@17 1814 * Returns true if the specified token position represents a reference.
Chris@17 1815 * Returns false if the token represents a bitwise operator.
Chris@17 1816 *
Chris@17 1817 * @param int $stackPtr The position of the T_BITWISE_AND token.
Chris@17 1818 *
Chris@17 1819 * @return boolean
Chris@17 1820 */
Chris@17 1821 public function isReference($stackPtr)
Chris@17 1822 {
Chris@17 1823 if ($this->tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
Chris@17 1824 return false;
Chris@17 1825 }
Chris@17 1826
Chris@17 1827 $tokenBefore = $this->findPrevious(
Chris@17 1828 Util\Tokens::$emptyTokens,
Chris@17 1829 ($stackPtr - 1),
Chris@17 1830 null,
Chris@17 1831 true
Chris@17 1832 );
Chris@17 1833
Chris@17 1834 if ($this->tokens[$tokenBefore]['code'] === T_FUNCTION) {
Chris@17 1835 // Function returns a reference.
Chris@17 1836 return true;
Chris@17 1837 }
Chris@17 1838
Chris@17 1839 if ($this->tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
Chris@17 1840 // Inside a foreach loop or array assignment, this is a reference.
Chris@17 1841 return true;
Chris@17 1842 }
Chris@17 1843
Chris@17 1844 if ($this->tokens[$tokenBefore]['code'] === T_AS) {
Chris@17 1845 // Inside a foreach loop, this is a reference.
Chris@17 1846 return true;
Chris@17 1847 }
Chris@17 1848
Chris@17 1849 if (isset(Util\Tokens::$assignmentTokens[$this->tokens[$tokenBefore]['code']]) === true) {
Chris@17 1850 // This is directly after an assignment. It's a reference. Even if
Chris@17 1851 // it is part of an operation, the other tests will handle it.
Chris@17 1852 return true;
Chris@17 1853 }
Chris@17 1854
Chris@17 1855 $tokenAfter = $this->findNext(
Chris@17 1856 Util\Tokens::$emptyTokens,
Chris@17 1857 ($stackPtr + 1),
Chris@17 1858 null,
Chris@17 1859 true
Chris@17 1860 );
Chris@17 1861
Chris@17 1862 if ($this->tokens[$tokenAfter]['code'] === T_NEW) {
Chris@17 1863 return true;
Chris@17 1864 }
Chris@17 1865
Chris@17 1866 if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === true) {
Chris@17 1867 $brackets = $this->tokens[$stackPtr]['nested_parenthesis'];
Chris@17 1868 $lastBracket = array_pop($brackets);
Chris@17 1869 if (isset($this->tokens[$lastBracket]['parenthesis_owner']) === true) {
Chris@17 1870 $owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']];
Chris@17 1871 if ($owner['code'] === T_FUNCTION
Chris@17 1872 || $owner['code'] === T_CLOSURE
Chris@17 1873 ) {
Chris@17 1874 $params = $this->getMethodParameters($this->tokens[$lastBracket]['parenthesis_owner']);
Chris@17 1875 foreach ($params as $param) {
Chris@17 1876 $varToken = $tokenAfter;
Chris@17 1877 if ($param['variable_length'] === true) {
Chris@17 1878 $varToken = $this->findNext(
Chris@17 1879 (Util\Tokens::$emptyTokens + [T_ELLIPSIS]),
Chris@17 1880 ($stackPtr + 1),
Chris@17 1881 null,
Chris@17 1882 true
Chris@17 1883 );
Chris@17 1884 }
Chris@17 1885
Chris@17 1886 if ($param['token'] === $varToken
Chris@17 1887 && $param['pass_by_reference'] === true
Chris@17 1888 ) {
Chris@17 1889 // Function parameter declared to be passed by reference.
Chris@17 1890 return true;
Chris@17 1891 }
Chris@17 1892 }
Chris@17 1893 }//end if
Chris@17 1894 } else {
Chris@17 1895 $prev = false;
Chris@17 1896 for ($t = ($this->tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) {
Chris@17 1897 if ($this->tokens[$t]['code'] !== T_WHITESPACE) {
Chris@17 1898 $prev = $t;
Chris@17 1899 break;
Chris@17 1900 }
Chris@17 1901 }
Chris@17 1902
Chris@17 1903 if ($prev !== false && $this->tokens[$prev]['code'] === T_USE) {
Chris@17 1904 // Closure use by reference.
Chris@17 1905 return true;
Chris@17 1906 }
Chris@17 1907 }//end if
Chris@17 1908 }//end if
Chris@17 1909
Chris@17 1910 // Pass by reference in function calls and assign by reference in arrays.
Chris@17 1911 if ($this->tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS
Chris@17 1912 || $this->tokens[$tokenBefore]['code'] === T_COMMA
Chris@17 1913 || $this->tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY
Chris@17 1914 ) {
Chris@17 1915 if ($this->tokens[$tokenAfter]['code'] === T_VARIABLE) {
Chris@17 1916 return true;
Chris@17 1917 } else {
Chris@17 1918 $skip = Util\Tokens::$emptyTokens;
Chris@17 1919 $skip[] = T_NS_SEPARATOR;
Chris@17 1920 $skip[] = T_SELF;
Chris@17 1921 $skip[] = T_PARENT;
Chris@17 1922 $skip[] = T_STATIC;
Chris@17 1923 $skip[] = T_STRING;
Chris@17 1924 $skip[] = T_NAMESPACE;
Chris@17 1925 $skip[] = T_DOUBLE_COLON;
Chris@17 1926
Chris@17 1927 $nextSignificantAfter = $this->findNext(
Chris@17 1928 $skip,
Chris@17 1929 ($stackPtr + 1),
Chris@17 1930 null,
Chris@17 1931 true
Chris@17 1932 );
Chris@17 1933 if ($this->tokens[$nextSignificantAfter]['code'] === T_VARIABLE) {
Chris@17 1934 return true;
Chris@17 1935 }
Chris@17 1936 }//end if
Chris@17 1937 }//end if
Chris@17 1938
Chris@17 1939 return false;
Chris@17 1940
Chris@17 1941 }//end isReference()
Chris@17 1942
Chris@17 1943
Chris@17 1944 /**
Chris@17 1945 * Returns the content of the tokens from the specified start position in
Chris@17 1946 * the token stack for the specified length.
Chris@17 1947 *
Chris@17 1948 * @param int $start The position to start from in the token stack.
Chris@17 1949 * @param int $length The length of tokens to traverse from the start pos.
Chris@17 1950 * @param bool $origContent Whether the original content or the tab replaced
Chris@17 1951 * content should be used.
Chris@17 1952 *
Chris@17 1953 * @return string The token contents.
Chris@17 1954 */
Chris@17 1955 public function getTokensAsString($start, $length, $origContent=false)
Chris@17 1956 {
Chris@17 1957 if (is_int($start) === false || isset($this->tokens[$start]) === false) {
Chris@17 1958 throw new RuntimeException('The $start position for getTokensAsString() must exist in the token stack');
Chris@17 1959 }
Chris@17 1960
Chris@17 1961 if (is_int($length) === false || $length <= 0) {
Chris@17 1962 return '';
Chris@17 1963 }
Chris@17 1964
Chris@17 1965 $str = '';
Chris@17 1966 $end = ($start + $length);
Chris@17 1967 if ($end > $this->numTokens) {
Chris@17 1968 $end = $this->numTokens;
Chris@17 1969 }
Chris@17 1970
Chris@17 1971 for ($i = $start; $i < $end; $i++) {
Chris@17 1972 // If tabs are being converted to spaces by the tokeniser, the
Chris@17 1973 // original content should be used instead of the converted content.
Chris@17 1974 if ($origContent === true && isset($this->tokens[$i]['orig_content']) === true) {
Chris@17 1975 $str .= $this->tokens[$i]['orig_content'];
Chris@17 1976 } else {
Chris@17 1977 $str .= $this->tokens[$i]['content'];
Chris@17 1978 }
Chris@17 1979 }
Chris@17 1980
Chris@17 1981 return $str;
Chris@17 1982
Chris@17 1983 }//end getTokensAsString()
Chris@17 1984
Chris@17 1985
Chris@17 1986 /**
Chris@17 1987 * Returns the position of the previous specified token(s).
Chris@17 1988 *
Chris@17 1989 * If a value is specified, the previous token of the specified type(s)
Chris@17 1990 * containing the specified value will be returned.
Chris@17 1991 *
Chris@17 1992 * Returns false if no token can be found.
Chris@17 1993 *
Chris@17 1994 * @param int|array $types The type(s) of tokens to search for.
Chris@17 1995 * @param int $start The position to start searching from in the
Chris@17 1996 * token stack.
Chris@17 1997 * @param int $end The end position to fail if no token is found.
Chris@17 1998 * if not specified or null, end will default to
Chris@17 1999 * the start of the token stack.
Chris@17 2000 * @param bool $exclude If true, find the previous token that is NOT of
Chris@17 2001 * the types specified in $types.
Chris@17 2002 * @param string $value The value that the token(s) must be equal to.
Chris@17 2003 * If value is omitted, tokens with any value will
Chris@17 2004 * be returned.
Chris@17 2005 * @param bool $local If true, tokens outside the current statement
Chris@17 2006 * will not be checked. IE. checking will stop
Chris@17 2007 * at the previous semi-colon found.
Chris@17 2008 *
Chris@17 2009 * @return int|bool
Chris@17 2010 * @see findNext()
Chris@17 2011 */
Chris@17 2012 public function findPrevious(
Chris@17 2013 $types,
Chris@17 2014 $start,
Chris@17 2015 $end=null,
Chris@17 2016 $exclude=false,
Chris@17 2017 $value=null,
Chris@17 2018 $local=false
Chris@17 2019 ) {
Chris@17 2020 $types = (array) $types;
Chris@17 2021
Chris@17 2022 if ($end === null) {
Chris@17 2023 $end = 0;
Chris@17 2024 }
Chris@17 2025
Chris@17 2026 for ($i = $start; $i >= $end; $i--) {
Chris@17 2027 $found = (bool) $exclude;
Chris@17 2028 foreach ($types as $type) {
Chris@17 2029 if ($this->tokens[$i]['code'] === $type) {
Chris@17 2030 $found = !$exclude;
Chris@17 2031 break;
Chris@17 2032 }
Chris@17 2033 }
Chris@17 2034
Chris@17 2035 if ($found === true) {
Chris@17 2036 if ($value === null) {
Chris@17 2037 return $i;
Chris@17 2038 } else if ($this->tokens[$i]['content'] === $value) {
Chris@17 2039 return $i;
Chris@17 2040 }
Chris@17 2041 }
Chris@17 2042
Chris@17 2043 if ($local === true) {
Chris@17 2044 if (isset($this->tokens[$i]['scope_opener']) === true
Chris@17 2045 && $i === $this->tokens[$i]['scope_closer']
Chris@17 2046 ) {
Chris@17 2047 $i = $this->tokens[$i]['scope_opener'];
Chris@17 2048 } else if (isset($this->tokens[$i]['bracket_opener']) === true
Chris@17 2049 && $i === $this->tokens[$i]['bracket_closer']
Chris@17 2050 ) {
Chris@17 2051 $i = $this->tokens[$i]['bracket_opener'];
Chris@17 2052 } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
Chris@17 2053 && $i === $this->tokens[$i]['parenthesis_closer']
Chris@17 2054 ) {
Chris@17 2055 $i = $this->tokens[$i]['parenthesis_opener'];
Chris@17 2056 } else if ($this->tokens[$i]['code'] === T_SEMICOLON) {
Chris@17 2057 break;
Chris@17 2058 }
Chris@17 2059 }
Chris@17 2060 }//end for
Chris@17 2061
Chris@17 2062 return false;
Chris@17 2063
Chris@17 2064 }//end findPrevious()
Chris@17 2065
Chris@17 2066
Chris@17 2067 /**
Chris@17 2068 * Returns the position of the next specified token(s).
Chris@17 2069 *
Chris@17 2070 * If a value is specified, the next token of the specified type(s)
Chris@17 2071 * containing the specified value will be returned.
Chris@17 2072 *
Chris@17 2073 * Returns false if no token can be found.
Chris@17 2074 *
Chris@17 2075 * @param int|array $types The type(s) of tokens to search for.
Chris@17 2076 * @param int $start The position to start searching from in the
Chris@17 2077 * token stack.
Chris@17 2078 * @param int $end The end position to fail if no token is found.
Chris@17 2079 * if not specified or null, end will default to
Chris@17 2080 * the end of the token stack.
Chris@17 2081 * @param bool $exclude If true, find the next token that is NOT of
Chris@17 2082 * a type specified in $types.
Chris@17 2083 * @param string $value The value that the token(s) must be equal to.
Chris@17 2084 * If value is omitted, tokens with any value will
Chris@17 2085 * be returned.
Chris@17 2086 * @param bool $local If true, tokens outside the current statement
Chris@17 2087 * will not be checked. i.e., checking will stop
Chris@17 2088 * at the next semi-colon found.
Chris@17 2089 *
Chris@17 2090 * @return int|bool
Chris@17 2091 * @see findPrevious()
Chris@17 2092 */
Chris@17 2093 public function findNext(
Chris@17 2094 $types,
Chris@17 2095 $start,
Chris@17 2096 $end=null,
Chris@17 2097 $exclude=false,
Chris@17 2098 $value=null,
Chris@17 2099 $local=false
Chris@17 2100 ) {
Chris@17 2101 $types = (array) $types;
Chris@17 2102
Chris@17 2103 if ($end === null || $end > $this->numTokens) {
Chris@17 2104 $end = $this->numTokens;
Chris@17 2105 }
Chris@17 2106
Chris@17 2107 for ($i = $start; $i < $end; $i++) {
Chris@17 2108 $found = (bool) $exclude;
Chris@17 2109 foreach ($types as $type) {
Chris@17 2110 if ($this->tokens[$i]['code'] === $type) {
Chris@17 2111 $found = !$exclude;
Chris@17 2112 break;
Chris@17 2113 }
Chris@17 2114 }
Chris@17 2115
Chris@17 2116 if ($found === true) {
Chris@17 2117 if ($value === null) {
Chris@17 2118 return $i;
Chris@17 2119 } else if ($this->tokens[$i]['content'] === $value) {
Chris@17 2120 return $i;
Chris@17 2121 }
Chris@17 2122 }
Chris@17 2123
Chris@17 2124 if ($local === true && $this->tokens[$i]['code'] === T_SEMICOLON) {
Chris@17 2125 break;
Chris@17 2126 }
Chris@17 2127 }//end for
Chris@17 2128
Chris@17 2129 return false;
Chris@17 2130
Chris@17 2131 }//end findNext()
Chris@17 2132
Chris@17 2133
Chris@17 2134 /**
Chris@17 2135 * Returns the position of the first non-whitespace token in a statement.
Chris@17 2136 *
Chris@17 2137 * @param int $start The position to start searching from in the token stack.
Chris@17 2138 * @param int|array $ignore Token types that should not be considered stop points.
Chris@17 2139 *
Chris@17 2140 * @return int
Chris@17 2141 */
Chris@17 2142 public function findStartOfStatement($start, $ignore=null)
Chris@17 2143 {
Chris@17 2144 $endTokens = Util\Tokens::$blockOpeners;
Chris@17 2145
Chris@17 2146 $endTokens[T_COLON] = true;
Chris@17 2147 $endTokens[T_COMMA] = true;
Chris@17 2148 $endTokens[T_DOUBLE_ARROW] = true;
Chris@17 2149 $endTokens[T_SEMICOLON] = true;
Chris@17 2150 $endTokens[T_OPEN_TAG] = true;
Chris@17 2151 $endTokens[T_CLOSE_TAG] = true;
Chris@17 2152 $endTokens[T_OPEN_SHORT_ARRAY] = true;
Chris@17 2153
Chris@17 2154 if ($ignore !== null) {
Chris@17 2155 $ignore = (array) $ignore;
Chris@17 2156 foreach ($ignore as $code) {
Chris@17 2157 if (isset($endTokens[$code]) === true) {
Chris@17 2158 unset($endTokens[$code]);
Chris@17 2159 }
Chris@17 2160 }
Chris@17 2161 }
Chris@17 2162
Chris@17 2163 $lastNotEmpty = $start;
Chris@17 2164
Chris@17 2165 for ($i = $start; $i >= 0; $i--) {
Chris@17 2166 if (isset($endTokens[$this->tokens[$i]['code']]) === true) {
Chris@17 2167 // Found the end of the previous statement.
Chris@17 2168 return $lastNotEmpty;
Chris@17 2169 }
Chris@17 2170
Chris@17 2171 if (isset($this->tokens[$i]['scope_opener']) === true
Chris@17 2172 && $i === $this->tokens[$i]['scope_closer']
Chris@17 2173 ) {
Chris@17 2174 // Found the end of the previous scope block.
Chris@17 2175 return $lastNotEmpty;
Chris@17 2176 }
Chris@17 2177
Chris@17 2178 // Skip nested statements.
Chris@17 2179 if (isset($this->tokens[$i]['bracket_opener']) === true
Chris@17 2180 && $i === $this->tokens[$i]['bracket_closer']
Chris@17 2181 ) {
Chris@17 2182 $i = $this->tokens[$i]['bracket_opener'];
Chris@17 2183 } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
Chris@17 2184 && $i === $this->tokens[$i]['parenthesis_closer']
Chris@17 2185 ) {
Chris@17 2186 $i = $this->tokens[$i]['parenthesis_opener'];
Chris@17 2187 }
Chris@17 2188
Chris@17 2189 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
Chris@17 2190 $lastNotEmpty = $i;
Chris@17 2191 }
Chris@17 2192 }//end for
Chris@17 2193
Chris@17 2194 return 0;
Chris@17 2195
Chris@17 2196 }//end findStartOfStatement()
Chris@17 2197
Chris@17 2198
Chris@17 2199 /**
Chris@17 2200 * Returns the position of the last non-whitespace token in a statement.
Chris@17 2201 *
Chris@17 2202 * @param int $start The position to start searching from in the token stack.
Chris@17 2203 * @param int|array $ignore Token types that should not be considered stop points.
Chris@17 2204 *
Chris@17 2205 * @return int
Chris@17 2206 */
Chris@17 2207 public function findEndOfStatement($start, $ignore=null)
Chris@17 2208 {
Chris@17 2209 $endTokens = [
Chris@17 2210 T_COLON => true,
Chris@17 2211 T_COMMA => true,
Chris@17 2212 T_DOUBLE_ARROW => true,
Chris@17 2213 T_SEMICOLON => true,
Chris@17 2214 T_CLOSE_PARENTHESIS => true,
Chris@17 2215 T_CLOSE_SQUARE_BRACKET => true,
Chris@17 2216 T_CLOSE_CURLY_BRACKET => true,
Chris@17 2217 T_CLOSE_SHORT_ARRAY => true,
Chris@17 2218 T_OPEN_TAG => true,
Chris@17 2219 T_CLOSE_TAG => true,
Chris@17 2220 ];
Chris@17 2221
Chris@17 2222 if ($ignore !== null) {
Chris@17 2223 $ignore = (array) $ignore;
Chris@17 2224 foreach ($ignore as $code) {
Chris@17 2225 if (isset($endTokens[$code]) === true) {
Chris@17 2226 unset($endTokens[$code]);
Chris@17 2227 }
Chris@17 2228 }
Chris@17 2229 }
Chris@17 2230
Chris@17 2231 $lastNotEmpty = $start;
Chris@17 2232
Chris@17 2233 for ($i = $start; $i < $this->numTokens; $i++) {
Chris@17 2234 if ($i !== $start && isset($endTokens[$this->tokens[$i]['code']]) === true) {
Chris@17 2235 // Found the end of the statement.
Chris@17 2236 if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
Chris@17 2237 || $this->tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
Chris@17 2238 || $this->tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
Chris@17 2239 || $this->tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
Chris@17 2240 || $this->tokens[$i]['code'] === T_OPEN_TAG
Chris@17 2241 || $this->tokens[$i]['code'] === T_CLOSE_TAG
Chris@17 2242 ) {
Chris@17 2243 return $lastNotEmpty;
Chris@17 2244 }
Chris@17 2245
Chris@17 2246 return $i;
Chris@17 2247 }
Chris@17 2248
Chris@17 2249 // Skip nested statements.
Chris@17 2250 if (isset($this->tokens[$i]['scope_closer']) === true
Chris@17 2251 && ($i === $this->tokens[$i]['scope_opener']
Chris@17 2252 || $i === $this->tokens[$i]['scope_condition'])
Chris@17 2253 ) {
Chris@17 2254 if ($i === $start && isset(Util\Tokens::$scopeOpeners[$this->tokens[$i]['code']]) === true) {
Chris@17 2255 return $this->tokens[$i]['scope_closer'];
Chris@17 2256 }
Chris@17 2257
Chris@17 2258 $i = $this->tokens[$i]['scope_closer'];
Chris@17 2259 } else if (isset($this->tokens[$i]['bracket_closer']) === true
Chris@17 2260 && $i === $this->tokens[$i]['bracket_opener']
Chris@17 2261 ) {
Chris@17 2262 $i = $this->tokens[$i]['bracket_closer'];
Chris@17 2263 } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
Chris@17 2264 && $i === $this->tokens[$i]['parenthesis_opener']
Chris@17 2265 ) {
Chris@17 2266 $i = $this->tokens[$i]['parenthesis_closer'];
Chris@17 2267 }
Chris@17 2268
Chris@17 2269 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
Chris@17 2270 $lastNotEmpty = $i;
Chris@17 2271 }
Chris@17 2272 }//end for
Chris@17 2273
Chris@17 2274 return ($this->numTokens - 1);
Chris@17 2275
Chris@17 2276 }//end findEndOfStatement()
Chris@17 2277
Chris@17 2278
Chris@17 2279 /**
Chris@17 2280 * Returns the position of the first token on a line, matching given type.
Chris@17 2281 *
Chris@17 2282 * Returns false if no token can be found.
Chris@17 2283 *
Chris@17 2284 * @param int|array $types The type(s) of tokens to search for.
Chris@17 2285 * @param int $start The position to start searching from in the
Chris@17 2286 * token stack. The first token matching on
Chris@17 2287 * this line before this token will be returned.
Chris@17 2288 * @param bool $exclude If true, find the token that is NOT of
Chris@17 2289 * the types specified in $types.
Chris@17 2290 * @param string $value The value that the token must be equal to.
Chris@17 2291 * If value is omitted, tokens with any value will
Chris@17 2292 * be returned.
Chris@17 2293 *
Chris@17 2294 * @return int | bool
Chris@17 2295 */
Chris@17 2296 public function findFirstOnLine($types, $start, $exclude=false, $value=null)
Chris@17 2297 {
Chris@17 2298 if (is_array($types) === false) {
Chris@17 2299 $types = [$types];
Chris@17 2300 }
Chris@17 2301
Chris@17 2302 $foundToken = false;
Chris@17 2303
Chris@17 2304 for ($i = $start; $i >= 0; $i--) {
Chris@17 2305 if ($this->tokens[$i]['line'] < $this->tokens[$start]['line']) {
Chris@17 2306 break;
Chris@17 2307 }
Chris@17 2308
Chris@17 2309 $found = $exclude;
Chris@17 2310 foreach ($types as $type) {
Chris@17 2311 if ($exclude === false) {
Chris@17 2312 if ($this->tokens[$i]['code'] === $type) {
Chris@17 2313 $found = true;
Chris@17 2314 break;
Chris@17 2315 }
Chris@17 2316 } else {
Chris@17 2317 if ($this->tokens[$i]['code'] === $type) {
Chris@17 2318 $found = false;
Chris@17 2319 break;
Chris@17 2320 }
Chris@17 2321 }
Chris@17 2322 }
Chris@17 2323
Chris@17 2324 if ($found === true) {
Chris@17 2325 if ($value === null) {
Chris@17 2326 $foundToken = $i;
Chris@17 2327 } else if ($this->tokens[$i]['content'] === $value) {
Chris@17 2328 $foundToken = $i;
Chris@17 2329 }
Chris@17 2330 }
Chris@17 2331 }//end for
Chris@17 2332
Chris@17 2333 return $foundToken;
Chris@17 2334
Chris@17 2335 }//end findFirstOnLine()
Chris@17 2336
Chris@17 2337
Chris@17 2338 /**
Chris@17 2339 * Determine if the passed token has a condition of one of the passed types.
Chris@17 2340 *
Chris@17 2341 * @param int $stackPtr The position of the token we are checking.
Chris@17 2342 * @param int|array $types The type(s) of tokens to search for.
Chris@17 2343 *
Chris@17 2344 * @return boolean
Chris@17 2345 */
Chris@17 2346 public function hasCondition($stackPtr, $types)
Chris@17 2347 {
Chris@17 2348 // Check for the existence of the token.
Chris@17 2349 if (isset($this->tokens[$stackPtr]) === false) {
Chris@17 2350 return false;
Chris@17 2351 }
Chris@17 2352
Chris@17 2353 // Make sure the token has conditions.
Chris@17 2354 if (isset($this->tokens[$stackPtr]['conditions']) === false) {
Chris@17 2355 return false;
Chris@17 2356 }
Chris@17 2357
Chris@17 2358 $types = (array) $types;
Chris@17 2359 $conditions = $this->tokens[$stackPtr]['conditions'];
Chris@17 2360
Chris@17 2361 foreach ($types as $type) {
Chris@17 2362 if (in_array($type, $conditions) === true) {
Chris@17 2363 // We found a token with the required type.
Chris@17 2364 return true;
Chris@17 2365 }
Chris@17 2366 }
Chris@17 2367
Chris@17 2368 return false;
Chris@17 2369
Chris@17 2370 }//end hasCondition()
Chris@17 2371
Chris@17 2372
Chris@17 2373 /**
Chris@17 2374 * Return the position of the condition for the passed token.
Chris@17 2375 *
Chris@17 2376 * Returns FALSE if the token does not have the condition.
Chris@17 2377 *
Chris@17 2378 * @param int $stackPtr The position of the token we are checking.
Chris@17 2379 * @param int $type The type of token to search for.
Chris@17 2380 *
Chris@17 2381 * @return int
Chris@17 2382 */
Chris@17 2383 public function getCondition($stackPtr, $type)
Chris@17 2384 {
Chris@17 2385 // Check for the existence of the token.
Chris@17 2386 if (isset($this->tokens[$stackPtr]) === false) {
Chris@17 2387 return false;
Chris@17 2388 }
Chris@17 2389
Chris@17 2390 // Make sure the token has conditions.
Chris@17 2391 if (isset($this->tokens[$stackPtr]['conditions']) === false) {
Chris@17 2392 return false;
Chris@17 2393 }
Chris@17 2394
Chris@17 2395 $conditions = $this->tokens[$stackPtr]['conditions'];
Chris@17 2396 foreach ($conditions as $token => $condition) {
Chris@17 2397 if ($condition === $type) {
Chris@17 2398 return $token;
Chris@17 2399 }
Chris@17 2400 }
Chris@17 2401
Chris@17 2402 return false;
Chris@17 2403
Chris@17 2404 }//end getCondition()
Chris@17 2405
Chris@17 2406
Chris@17 2407 /**
Chris@17 2408 * Returns the name of the class that the specified class extends.
Chris@17 2409 * (works for classes, anonymous classes and interfaces)
Chris@17 2410 *
Chris@17 2411 * Returns FALSE on error or if there is no extended class name.
Chris@17 2412 *
Chris@17 2413 * @param int $stackPtr The stack position of the class.
Chris@17 2414 *
Chris@17 2415 * @return string|false
Chris@17 2416 */
Chris@17 2417 public function findExtendedClassName($stackPtr)
Chris@17 2418 {
Chris@17 2419 // Check for the existence of the token.
Chris@17 2420 if (isset($this->tokens[$stackPtr]) === false) {
Chris@17 2421 return false;
Chris@17 2422 }
Chris@17 2423
Chris@17 2424 if ($this->tokens[$stackPtr]['code'] !== T_CLASS
Chris@17 2425 && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
Chris@17 2426 && $this->tokens[$stackPtr]['code'] !== T_INTERFACE
Chris@17 2427 ) {
Chris@17 2428 return false;
Chris@17 2429 }
Chris@17 2430
Chris@17 2431 if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
Chris@17 2432 return false;
Chris@17 2433 }
Chris@17 2434
Chris@17 2435 $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
Chris@17 2436 $extendsIndex = $this->findNext(T_EXTENDS, $stackPtr, $classOpenerIndex);
Chris@17 2437 if (false === $extendsIndex) {
Chris@17 2438 return false;
Chris@17 2439 }
Chris@17 2440
Chris@17 2441 $find = [
Chris@17 2442 T_NS_SEPARATOR,
Chris@17 2443 T_STRING,
Chris@17 2444 T_WHITESPACE,
Chris@17 2445 ];
Chris@17 2446
Chris@17 2447 $end = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true);
Chris@17 2448 $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
Chris@17 2449 $name = trim($name);
Chris@17 2450
Chris@17 2451 if ($name === '') {
Chris@17 2452 return false;
Chris@17 2453 }
Chris@17 2454
Chris@17 2455 return $name;
Chris@17 2456
Chris@17 2457 }//end findExtendedClassName()
Chris@17 2458
Chris@17 2459
Chris@17 2460 /**
Chris@17 2461 * Returns the names of the interfaces that the specified class implements.
Chris@17 2462 *
Chris@17 2463 * Returns FALSE on error or if there are no implemented interface names.
Chris@17 2464 *
Chris@17 2465 * @param int $stackPtr The stack position of the class.
Chris@17 2466 *
Chris@17 2467 * @return array|false
Chris@17 2468 */
Chris@17 2469 public function findImplementedInterfaceNames($stackPtr)
Chris@17 2470 {
Chris@17 2471 // Check for the existence of the token.
Chris@17 2472 if (isset($this->tokens[$stackPtr]) === false) {
Chris@17 2473 return false;
Chris@17 2474 }
Chris@17 2475
Chris@17 2476 if ($this->tokens[$stackPtr]['code'] !== T_CLASS
Chris@17 2477 && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
Chris@17 2478 ) {
Chris@17 2479 return false;
Chris@17 2480 }
Chris@17 2481
Chris@17 2482 if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
Chris@17 2483 return false;
Chris@17 2484 }
Chris@17 2485
Chris@17 2486 $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
Chris@17 2487 $implementsIndex = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
Chris@17 2488 if ($implementsIndex === false) {
Chris@17 2489 return false;
Chris@17 2490 }
Chris@17 2491
Chris@17 2492 $find = [
Chris@17 2493 T_NS_SEPARATOR,
Chris@17 2494 T_STRING,
Chris@17 2495 T_WHITESPACE,
Chris@17 2496 T_COMMA,
Chris@17 2497 ];
Chris@17 2498
Chris@17 2499 $end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
Chris@17 2500 $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
Chris@17 2501 $name = trim($name);
Chris@17 2502
Chris@17 2503 if ($name === '') {
Chris@17 2504 return false;
Chris@17 2505 } else {
Chris@17 2506 $names = explode(',', $name);
Chris@17 2507 $names = array_map('trim', $names);
Chris@17 2508 return $names;
Chris@17 2509 }
Chris@17 2510
Chris@17 2511 }//end findImplementedInterfaceNames()
Chris@17 2512
Chris@17 2513
Chris@17 2514 }//end class