annotate vendor/squizlabs/php_codesniffer/src/Files/File.php @ 19:fa3358dc1485 tip

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