annotate vendor/squizlabs/php_codesniffer/CodeSniffer.php @ 2:5311817fb629

Theme updates
author Chris Cannam
date Tue, 10 Jul 2018 13:19:18 +0000
parents c75dbcec494b
children
rev   line source
Chris@0 1 <?php
Chris@0 2 /**
Chris@0 3 * PHP_CodeSniffer tokenizes PHP code and detects violations of a
Chris@0 4 * defined set of coding standards.
Chris@0 5 *
Chris@0 6 * PHP version 5
Chris@0 7 *
Chris@0 8 * @category PHP
Chris@0 9 * @package PHP_CodeSniffer
Chris@0 10 * @author Greg Sherwood <gsherwood@squiz.net>
Chris@0 11 * @author Marc McIntyre <mmcintyre@squiz.net>
Chris@0 12 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
Chris@0 13 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
Chris@0 14 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@0 15 */
Chris@0 16
Chris@0 17 spl_autoload_register(array('PHP_CodeSniffer', 'autoload'));
Chris@0 18
Chris@0 19 if (class_exists('PHP_CodeSniffer_Exception', true) === false) {
Chris@0 20 throw new Exception('Class PHP_CodeSniffer_Exception not found');
Chris@0 21 }
Chris@0 22
Chris@0 23 if (class_exists('PHP_CodeSniffer_File', true) === false) {
Chris@0 24 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found');
Chris@0 25 }
Chris@0 26
Chris@0 27 if (class_exists('PHP_CodeSniffer_Fixer', true) === false) {
Chris@0 28 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Fixer not found');
Chris@0 29 }
Chris@0 30
Chris@0 31 if (class_exists('PHP_CodeSniffer_Tokens', true) === false) {
Chris@0 32 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found');
Chris@0 33 }
Chris@0 34
Chris@0 35 if (class_exists('PHP_CodeSniffer_CLI', true) === false) {
Chris@0 36 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CLI not found');
Chris@0 37 }
Chris@0 38
Chris@0 39 if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) {
Chris@0 40 throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found');
Chris@0 41 }
Chris@0 42
Chris@0 43 /**
Chris@0 44 * PHP_CodeSniffer tokenizes PHP code and detects violations of a
Chris@0 45 * defined set of coding standards.
Chris@0 46 *
Chris@0 47 * Standards are specified by classes that implement the PHP_CodeSniffer_Sniff
Chris@0 48 * interface. A sniff registers what token types it wishes to listen for, then
Chris@0 49 * PHP_CodeSniffer encounters that token, the sniff is invoked and passed
Chris@0 50 * information about where the token was found in the stack, and the token stack
Chris@0 51 * itself.
Chris@0 52 *
Chris@0 53 * Sniff files and their containing class must be prefixed with Sniff, and
Chris@0 54 * have an extension of .php.
Chris@0 55 *
Chris@0 56 * Multiple PHP_CodeSniffer operations can be performed by re-calling the
Chris@0 57 * process function with different parameters.
Chris@0 58 *
Chris@0 59 * @category PHP
Chris@0 60 * @package PHP_CodeSniffer
Chris@0 61 * @author Greg Sherwood <gsherwood@squiz.net>
Chris@0 62 * @author Marc McIntyre <mmcintyre@squiz.net>
Chris@0 63 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
Chris@0 64 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
Chris@0 65 * @version Release: @package_version@
Chris@0 66 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@0 67 */
Chris@0 68 class PHP_CodeSniffer
Chris@0 69 {
Chris@0 70
Chris@0 71 /**
Chris@0 72 * The current version.
Chris@0 73 *
Chris@0 74 * @var string
Chris@0 75 */
Chris@2 76 const VERSION = '2.9.1';
Chris@0 77
Chris@0 78 /**
Chris@0 79 * Package stability; either stable, beta or alpha.
Chris@0 80 *
Chris@0 81 * @var string
Chris@0 82 */
Chris@0 83 const STABILITY = 'stable';
Chris@0 84
Chris@0 85 /**
Chris@0 86 * The file or directory that is currently being processed.
Chris@0 87 *
Chris@0 88 * @var string
Chris@0 89 */
Chris@0 90 protected $file = '';
Chris@0 91
Chris@0 92 /**
Chris@0 93 * The directories that the processed rulesets are in.
Chris@0 94 *
Chris@0 95 * This is declared static because it is also used in the
Chris@0 96 * autoloader to look for sniffs outside the PHPCS install.
Chris@0 97 * This way, standards designed to be installed inside PHPCS can
Chris@0 98 * also be used from outside the PHPCS Standards directory.
Chris@0 99 *
Chris@0 100 * @var string
Chris@0 101 */
Chris@0 102 protected static $rulesetDirs = array();
Chris@0 103
Chris@0 104 /**
Chris@0 105 * The CLI object controlling the run.
Chris@0 106 *
Chris@0 107 * @var PHP_CodeSniffer_CLI
Chris@0 108 */
Chris@0 109 public $cli = null;
Chris@0 110
Chris@0 111 /**
Chris@0 112 * The Reporting object controlling report generation.
Chris@0 113 *
Chris@0 114 * @var PHP_CodeSniffer_Reporting
Chris@0 115 */
Chris@0 116 public $reporting = null;
Chris@0 117
Chris@0 118 /**
Chris@0 119 * An array of sniff objects that are being used to check files.
Chris@0 120 *
Chris@0 121 * @var array(PHP_CodeSniffer_Sniff)
Chris@0 122 */
Chris@0 123 protected $listeners = array();
Chris@0 124
Chris@0 125 /**
Chris@0 126 * An array of sniffs that are being used to check files.
Chris@0 127 *
Chris@0 128 * @var array(string)
Chris@0 129 */
Chris@0 130 protected $sniffs = array();
Chris@0 131
Chris@0 132 /**
Chris@0 133 * A mapping of sniff codes to fully qualified class names.
Chris@0 134 *
Chris@0 135 * The key is the sniff code and the value
Chris@0 136 * is the fully qualified name of the sniff class.
Chris@0 137 *
Chris@0 138 * @var array<string, string>
Chris@0 139 */
Chris@0 140 public $sniffCodes = array();
Chris@0 141
Chris@0 142 /**
Chris@0 143 * The listeners array, indexed by token type.
Chris@0 144 *
Chris@0 145 * @var array
Chris@0 146 */
Chris@0 147 private $_tokenListeners = array();
Chris@0 148
Chris@0 149 /**
Chris@0 150 * An array of rules from the ruleset.xml file.
Chris@0 151 *
Chris@0 152 * It may be empty, indicating that the ruleset does not override
Chris@0 153 * any of the default sniff settings.
Chris@0 154 *
Chris@0 155 * @var array
Chris@0 156 */
Chris@0 157 protected $ruleset = array();
Chris@0 158
Chris@0 159 /**
Chris@0 160 * An array of patterns to use for skipping files.
Chris@0 161 *
Chris@0 162 * @var array
Chris@0 163 */
Chris@0 164 protected $ignorePatterns = array();
Chris@0 165
Chris@0 166 /**
Chris@0 167 * An array of extensions for files we will check.
Chris@0 168 *
Chris@0 169 * @var array
Chris@0 170 */
Chris@0 171 public $allowedFileExtensions = array();
Chris@0 172
Chris@0 173 /**
Chris@0 174 * An array of default extensions and associated tokenizers.
Chris@0 175 *
Chris@0 176 * If no extensions are set, these will be used as the defaults.
Chris@0 177 * If extensions are set, these will be used when the correct tokenizer
Chris@0 178 * can not be determined, such as when checking a passed filename instead
Chris@0 179 * of files in a directory.
Chris@0 180 *
Chris@0 181 * @var array
Chris@0 182 */
Chris@0 183 public $defaultFileExtensions = array(
Chris@0 184 'php' => 'PHP',
Chris@0 185 'inc' => 'PHP',
Chris@0 186 'js' => 'JS',
Chris@0 187 'css' => 'CSS',
Chris@0 188 );
Chris@0 189
Chris@0 190 /**
Chris@0 191 * An array of variable types for param/var we will check.
Chris@0 192 *
Chris@0 193 * @var array(string)
Chris@0 194 */
Chris@0 195 public static $allowedTypes = array(
Chris@0 196 'array',
Chris@0 197 'boolean',
Chris@0 198 'float',
Chris@0 199 'integer',
Chris@0 200 'mixed',
Chris@0 201 'object',
Chris@0 202 'string',
Chris@0 203 'resource',
Chris@0 204 'callable',
Chris@0 205 );
Chris@0 206
Chris@0 207
Chris@0 208 /**
Chris@0 209 * Constructs a PHP_CodeSniffer object.
Chris@0 210 *
Chris@0 211 * @param int $verbosity The verbosity level.
Chris@0 212 * 1: Print progress information.
Chris@0 213 * 2: Print tokenizer debug information.
Chris@0 214 * 3: Print sniff debug information.
Chris@0 215 * @param int $tabWidth The number of spaces each tab represents.
Chris@0 216 * If greater than zero, tabs will be replaced
Chris@0 217 * by spaces before testing each file.
Chris@0 218 * @param string $encoding The charset of the sniffed files.
Chris@0 219 * This is important for some reports that output
Chris@0 220 * with utf-8 encoding as you don't want it double
Chris@0 221 * encoding messages.
Chris@0 222 * @param bool $interactive If TRUE, will stop after each file with errors
Chris@0 223 * and wait for user input.
Chris@0 224 *
Chris@0 225 * @see process()
Chris@0 226 */
Chris@0 227 public function __construct(
Chris@0 228 $verbosity=0,
Chris@0 229 $tabWidth=0,
Chris@0 230 $encoding='iso-8859-1',
Chris@0 231 $interactive=false
Chris@0 232 ) {
Chris@0 233 if ($verbosity !== null) {
Chris@0 234 $this->setVerbosity($verbosity);
Chris@0 235 }
Chris@0 236
Chris@0 237 if ($tabWidth !== null) {
Chris@0 238 $this->setTabWidth($tabWidth);
Chris@0 239 }
Chris@0 240
Chris@0 241 if ($encoding !== null) {
Chris@0 242 $this->setEncoding($encoding);
Chris@0 243 }
Chris@0 244
Chris@0 245 if ($interactive !== null) {
Chris@0 246 $this->setInteractive($interactive);
Chris@0 247 }
Chris@0 248
Chris@0 249 if (defined('PHPCS_DEFAULT_ERROR_SEV') === false) {
Chris@0 250 define('PHPCS_DEFAULT_ERROR_SEV', 5);
Chris@0 251 }
Chris@0 252
Chris@0 253 if (defined('PHPCS_DEFAULT_WARN_SEV') === false) {
Chris@0 254 define('PHPCS_DEFAULT_WARN_SEV', 5);
Chris@0 255 }
Chris@0 256
Chris@0 257 if (defined('PHP_CODESNIFFER_CBF') === false) {
Chris@0 258 define('PHP_CODESNIFFER_CBF', false);
Chris@0 259 }
Chris@0 260
Chris@0 261 // Set default CLI object in case someone is running us
Chris@0 262 // without using the command line script.
Chris@0 263 $this->cli = new PHP_CodeSniffer_CLI();
Chris@0 264 $this->cli->errorSeverity = PHPCS_DEFAULT_ERROR_SEV;
Chris@0 265 $this->cli->warningSeverity = PHPCS_DEFAULT_WARN_SEV;
Chris@0 266 $this->cli->dieOnUnknownArg = false;
Chris@0 267
Chris@0 268 $this->reporting = new PHP_CodeSniffer_Reporting();
Chris@0 269
Chris@0 270 }//end __construct()
Chris@0 271
Chris@0 272
Chris@0 273 /**
Chris@0 274 * Autoload static method for loading classes and interfaces.
Chris@0 275 *
Chris@0 276 * @param string $className The name of the class or interface.
Chris@0 277 *
Chris@0 278 * @return void
Chris@0 279 */
Chris@0 280 public static function autoload($className)
Chris@0 281 {
Chris@0 282 if (substr($className, 0, 4) === 'PHP_') {
Chris@0 283 $newClassName = substr($className, 4);
Chris@0 284 } else {
Chris@0 285 $newClassName = $className;
Chris@0 286 }
Chris@0 287
Chris@0 288 $path = str_replace(array('_', '\\'), DIRECTORY_SEPARATOR, $newClassName).'.php';
Chris@0 289
Chris@0 290 if (is_file(dirname(__FILE__).DIRECTORY_SEPARATOR.$path) === true) {
Chris@0 291 // Check standard file locations based on class name.
Chris@0 292 include dirname(__FILE__).DIRECTORY_SEPARATOR.$path;
Chris@0 293 return;
Chris@0 294 } else {
Chris@0 295 // Check for included sniffs.
Chris@0 296 $installedPaths = PHP_CodeSniffer::getInstalledStandardPaths();
Chris@0 297 foreach ($installedPaths as $installedPath) {
Chris@0 298 if (is_file($installedPath.DIRECTORY_SEPARATOR.$path) === true) {
Chris@0 299 include $installedPath.DIRECTORY_SEPARATOR.$path;
Chris@0 300 return;
Chris@0 301 }
Chris@0 302 }
Chris@0 303
Chris@0 304 // Check standard file locations based on the loaded rulesets.
Chris@0 305 foreach (self::$rulesetDirs as $rulesetDir) {
Chris@0 306 if (is_file(dirname($rulesetDir).DIRECTORY_SEPARATOR.$path) === true) {
Chris@0 307 include_once dirname($rulesetDir).DIRECTORY_SEPARATOR.$path;
Chris@0 308 return;
Chris@0 309 }
Chris@0 310 }
Chris@0 311 }//end if
Chris@0 312
Chris@0 313 // Everything else.
Chris@0 314 @include $path;
Chris@0 315
Chris@0 316 }//end autoload()
Chris@0 317
Chris@0 318
Chris@0 319 /**
Chris@0 320 * Sets the verbosity level.
Chris@0 321 *
Chris@0 322 * @param int $verbosity The verbosity level.
Chris@0 323 * 1: Print progress information.
Chris@0 324 * 2: Print tokenizer debug information.
Chris@0 325 * 3: Print sniff debug information.
Chris@0 326 *
Chris@0 327 * @return void
Chris@0 328 */
Chris@0 329 public function setVerbosity($verbosity)
Chris@0 330 {
Chris@0 331 if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
Chris@0 332 define('PHP_CODESNIFFER_VERBOSITY', $verbosity);
Chris@0 333 }
Chris@0 334
Chris@0 335 }//end setVerbosity()
Chris@0 336
Chris@0 337
Chris@0 338 /**
Chris@0 339 * Sets the tab width.
Chris@0 340 *
Chris@0 341 * @param int $tabWidth The number of spaces each tab represents.
Chris@0 342 * If greater than zero, tabs will be replaced
Chris@0 343 * by spaces before testing each file.
Chris@0 344 *
Chris@0 345 * @return void
Chris@0 346 */
Chris@0 347 public function setTabWidth($tabWidth)
Chris@0 348 {
Chris@0 349 if (defined('PHP_CODESNIFFER_TAB_WIDTH') === false) {
Chris@0 350 define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth);
Chris@0 351 }
Chris@0 352
Chris@0 353 }//end setTabWidth()
Chris@0 354
Chris@0 355
Chris@0 356 /**
Chris@0 357 * Sets the encoding.
Chris@0 358 *
Chris@0 359 * @param string $encoding The charset of the sniffed files.
Chris@0 360 * This is important for some reports that output
Chris@0 361 * with utf-8 encoding as you don't want it double
Chris@0 362 * encoding messages.
Chris@0 363 *
Chris@0 364 * @return void
Chris@0 365 */
Chris@0 366 public function setEncoding($encoding)
Chris@0 367 {
Chris@0 368 if (defined('PHP_CODESNIFFER_ENCODING') === false) {
Chris@0 369 define('PHP_CODESNIFFER_ENCODING', $encoding);
Chris@0 370 }
Chris@0 371
Chris@0 372 }//end setEncoding()
Chris@0 373
Chris@0 374
Chris@0 375 /**
Chris@0 376 * Sets the interactive flag.
Chris@0 377 *
Chris@0 378 * @param bool $interactive If TRUE, will stop after each file with errors
Chris@0 379 * and wait for user input.
Chris@0 380 *
Chris@0 381 * @return void
Chris@0 382 */
Chris@0 383 public function setInteractive($interactive)
Chris@0 384 {
Chris@0 385 if (defined('PHP_CODESNIFFER_INTERACTIVE') === false) {
Chris@0 386 define('PHP_CODESNIFFER_INTERACTIVE', $interactive);
Chris@0 387 }
Chris@0 388
Chris@0 389 }//end setInteractive()
Chris@0 390
Chris@0 391
Chris@0 392 /**
Chris@0 393 * Sets an array of file extensions that we will allow checking of.
Chris@0 394 *
Chris@0 395 * If the extension is one of the defaults, a specific tokenizer
Chris@0 396 * will be used. Otherwise, the PHP tokenizer will be used for
Chris@0 397 * all extensions passed.
Chris@0 398 *
Chris@0 399 * @param array $extensions An array of file extensions.
Chris@0 400 *
Chris@0 401 * @return void
Chris@0 402 */
Chris@0 403 public function setAllowedFileExtensions(array $extensions)
Chris@0 404 {
Chris@0 405 $newExtensions = array();
Chris@0 406 foreach ($extensions as $ext) {
Chris@0 407 $slash = strpos($ext, '/');
Chris@0 408 if ($slash !== false) {
Chris@0 409 // They specified the tokenizer too.
Chris@0 410 list($ext, $tokenizer) = explode('/', $ext);
Chris@0 411 $newExtensions[$ext] = strtoupper($tokenizer);
Chris@0 412 continue;
Chris@0 413 }
Chris@0 414
Chris@0 415 if (isset($this->allowedFileExtensions[$ext]) === true) {
Chris@0 416 $newExtensions[$ext] = $this->allowedFileExtensions[$ext];
Chris@0 417 } else if (isset($this->defaultFileExtensions[$ext]) === true) {
Chris@0 418 $newExtensions[$ext] = $this->defaultFileExtensions[$ext];
Chris@0 419 } else {
Chris@0 420 $newExtensions[$ext] = 'PHP';
Chris@0 421 }
Chris@0 422 }
Chris@0 423
Chris@0 424 $this->allowedFileExtensions = $newExtensions;
Chris@0 425
Chris@0 426 }//end setAllowedFileExtensions()
Chris@0 427
Chris@0 428
Chris@0 429 /**
Chris@0 430 * Sets an array of ignore patterns that we use to skip files and folders.
Chris@0 431 *
Chris@0 432 * Patterns are not case sensitive.
Chris@0 433 *
Chris@0 434 * @param array $patterns An array of ignore patterns. The pattern is the key
Chris@0 435 * and the value is either "absolute" or "relative",
Chris@0 436 * depending on how the pattern should be applied to a
Chris@0 437 * file path.
Chris@0 438 *
Chris@0 439 * @return void
Chris@0 440 */
Chris@0 441 public function setIgnorePatterns(array $patterns)
Chris@0 442 {
Chris@0 443 $this->ignorePatterns = $patterns;
Chris@0 444
Chris@0 445 }//end setIgnorePatterns()
Chris@0 446
Chris@0 447
Chris@0 448 /**
Chris@0 449 * Gets the array of ignore patterns.
Chris@0 450 *
Chris@0 451 * Optionally takes a listener to get ignore patterns specified
Chris@0 452 * for that sniff only.
Chris@0 453 *
Chris@0 454 * @param string $listener The listener to get patterns for. If NULL, all
Chris@0 455 * patterns are returned.
Chris@0 456 *
Chris@0 457 * @return array
Chris@0 458 */
Chris@0 459 public function getIgnorePatterns($listener=null)
Chris@0 460 {
Chris@0 461 if ($listener === null) {
Chris@0 462 return $this->ignorePatterns;
Chris@0 463 }
Chris@0 464
Chris@0 465 if (isset($this->ignorePatterns[$listener]) === true) {
Chris@0 466 return $this->ignorePatterns[$listener];
Chris@0 467 }
Chris@0 468
Chris@0 469 return array();
Chris@0 470
Chris@0 471 }//end getIgnorePatterns()
Chris@0 472
Chris@0 473
Chris@0 474 /**
Chris@0 475 * Sets the internal CLI object.
Chris@0 476 *
Chris@0 477 * @param object $cli The CLI object controlling the run.
Chris@0 478 *
Chris@0 479 * @return void
Chris@0 480 */
Chris@0 481 public function setCli($cli)
Chris@0 482 {
Chris@0 483 $this->cli = $cli;
Chris@0 484
Chris@0 485 }//end setCli()
Chris@0 486
Chris@0 487
Chris@0 488 /**
Chris@0 489 * Start a PHP_CodeSniffer run.
Chris@0 490 *
Chris@0 491 * @param string|array $files The files and directories to process. For
Chris@0 492 * directories, each sub directory will also
Chris@0 493 * be traversed for source files.
Chris@0 494 * @param string|array $standards The set of code sniffs we are testing
Chris@0 495 * against.
Chris@0 496 * @param array $restrictions The sniff codes to restrict the
Chris@0 497 * violations to.
Chris@0 498 * @param boolean $local If true, don't recurse into directories.
Chris@0 499 *
Chris@0 500 * @return void
Chris@0 501 */
Chris@0 502 public function process($files, $standards, array $restrictions=array(), $local=false)
Chris@0 503 {
Chris@0 504 $files = (array) $files;
Chris@0 505 $this->initStandard($standards, $restrictions);
Chris@0 506 $this->processFiles($files, $local);
Chris@0 507
Chris@0 508 }//end process()
Chris@0 509
Chris@0 510
Chris@0 511 /**
Chris@0 512 * Initialise the standard that the run will use.
Chris@0 513 *
Chris@0 514 * @param string|array $standards The set of code sniffs we are testing
Chris@0 515 * against.
Chris@0 516 * @param array $restrictions The sniff codes to restrict the testing to.
Chris@0 517 * @param array $exclusions The sniff codes to exclude from testing.
Chris@0 518 *
Chris@0 519 * @return void
Chris@0 520 */
Chris@0 521 public function initStandard($standards, array $restrictions=array(), array $exclusions=array())
Chris@0 522 {
Chris@0 523 $standards = (array) $standards;
Chris@0 524
Chris@0 525 // Reset the members.
Chris@0 526 $this->listeners = array();
Chris@0 527 $this->sniffs = array();
Chris@0 528 $this->ruleset = array();
Chris@0 529 $this->_tokenListeners = array();
Chris@0 530 self::$rulesetDirs = array();
Chris@0 531
Chris@0 532 // Ensure this option is enabled or else line endings will not always
Chris@0 533 // be detected properly for files created on a Mac with the /r line ending.
Chris@0 534 ini_set('auto_detect_line_endings', true);
Chris@0 535
Chris@0 536 if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) {
Chris@0 537 // Should be one standard and one sniff being tested at a time.
Chris@0 538 $installed = $this->getInstalledStandardPath($standards[0]);
Chris@0 539 if ($installed !== null) {
Chris@0 540 $standard = $installed;
Chris@0 541 } else {
Chris@0 542 $standard = self::realpath($standards[0]);
Chris@0 543 if (is_dir($standard) === true
Chris@0 544 && is_file(self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true
Chris@0 545 ) {
Chris@0 546 $standard = self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml');
Chris@0 547 }
Chris@0 548 }
Chris@0 549
Chris@0 550 $sniffs = $this->_expandRulesetReference($restrictions[0], dirname($standard));
Chris@0 551 } else {
Chris@0 552 $sniffs = array();
Chris@0 553 foreach ($standards as $standard) {
Chris@0 554 $installed = $this->getInstalledStandardPath($standard);
Chris@0 555 if ($installed !== null) {
Chris@0 556 $standard = $installed;
Chris@0 557 } else {
Chris@0 558 $standard = self::realpath($standard);
Chris@0 559 if (is_dir($standard) === true
Chris@0 560 && is_file(self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true
Chris@0 561 ) {
Chris@0 562 $standard = self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml');
Chris@0 563 }
Chris@0 564 }
Chris@0 565
Chris@0 566 if (PHP_CODESNIFFER_VERBOSITY === 1) {
Chris@0 567 $ruleset = simplexml_load_string(file_get_contents($standard));
Chris@0 568 if ($ruleset !== false) {
Chris@0 569 $standardName = (string) $ruleset['name'];
Chris@0 570 }
Chris@0 571
Chris@0 572 echo "Registering sniffs in the $standardName standard... ";
Chris@0 573 if (count($standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@0 574 echo PHP_EOL;
Chris@0 575 }
Chris@0 576 }
Chris@0 577
Chris@0 578 $sniffs = array_merge($sniffs, $this->processRuleset($standard));
Chris@0 579 }//end foreach
Chris@0 580 }//end if
Chris@0 581
Chris@0 582 $sniffRestrictions = array();
Chris@0 583 foreach ($restrictions as $sniffCode) {
Chris@0 584 $parts = explode('.', strtolower($sniffCode));
Chris@0 585 $sniffRestrictions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff';
Chris@0 586 }
Chris@0 587
Chris@0 588 $sniffExclusions = array();
Chris@0 589 foreach ($exclusions as $sniffCode) {
Chris@0 590 $parts = explode('.', strtolower($sniffCode));
Chris@0 591 $sniffExclusions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff';
Chris@0 592 }
Chris@0 593
Chris@0 594 $this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions);
Chris@0 595 $this->populateTokenListeners();
Chris@0 596
Chris@0 597 if (PHP_CODESNIFFER_VERBOSITY === 1) {
Chris@0 598 $numSniffs = count($this->sniffs);
Chris@0 599 echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
Chris@0 600 }
Chris@0 601
Chris@0 602 }//end initStandard()
Chris@0 603
Chris@0 604
Chris@0 605 /**
Chris@0 606 * Processes the files/directories that PHP_CodeSniffer was constructed with.
Chris@0 607 *
Chris@0 608 * @param string|array $files The files and directories to process. For
Chris@0 609 * directories, each sub directory will also
Chris@0 610 * be traversed for source files.
Chris@0 611 * @param boolean $local If true, don't recurse into directories.
Chris@0 612 *
Chris@0 613 * @return void
Chris@0 614 * @throws PHP_CodeSniffer_Exception If files are invalid.
Chris@0 615 */
Chris@0 616 public function processFiles($files, $local=false)
Chris@0 617 {
Chris@0 618 $files = (array) $files;
Chris@0 619 $cliValues = $this->cli->getCommandLineValues();
Chris@0 620 $showProgress = $cliValues['showProgress'];
Chris@0 621 $useColors = $cliValues['colors'];
Chris@0 622
Chris@0 623 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@0 624 echo 'Creating file list... ';
Chris@0 625 }
Chris@0 626
Chris@0 627 if (empty($this->allowedFileExtensions) === true) {
Chris@0 628 $this->allowedFileExtensions = $this->defaultFileExtensions;
Chris@0 629 }
Chris@0 630
Chris@0 631 $todo = $this->getFilesToProcess($files, $local);
Chris@0 632 $numFiles = count($todo);
Chris@0 633
Chris@0 634 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@0 635 echo "DONE ($numFiles files in queue)".PHP_EOL;
Chris@0 636 }
Chris@0 637
Chris@0 638 $numProcessed = 0;
Chris@0 639 $dots = 0;
Chris@0 640 $maxLength = strlen($numFiles);
Chris@0 641 $lastDir = '';
Chris@0 642 foreach ($todo as $file) {
Chris@0 643 $this->file = $file;
Chris@0 644 $currDir = dirname($file);
Chris@0 645 if ($lastDir !== $currDir) {
Chris@0 646 if (PHP_CODESNIFFER_VERBOSITY > 0 || PHP_CODESNIFFER_CBF === true) {
Chris@0 647 echo 'Changing into directory '.$currDir.PHP_EOL;
Chris@0 648 }
Chris@0 649
Chris@0 650 $lastDir = $currDir;
Chris@0 651 }
Chris@0 652
Chris@0 653 $phpcsFile = $this->processFile($file, null);
Chris@0 654 $numProcessed++;
Chris@0 655
Chris@0 656 if (PHP_CODESNIFFER_VERBOSITY > 0
Chris@0 657 || PHP_CODESNIFFER_INTERACTIVE === true
Chris@0 658 || $showProgress === false
Chris@0 659 ) {
Chris@0 660 continue;
Chris@0 661 }
Chris@0 662
Chris@0 663 // Show progress information.
Chris@0 664 if ($phpcsFile === null) {
Chris@0 665 echo 'S';
Chris@0 666 } else {
Chris@0 667 $errors = $phpcsFile->getErrorCount();
Chris@0 668 $warnings = $phpcsFile->getWarningCount();
Chris@0 669 if ($errors > 0) {
Chris@0 670 if ($useColors === true) {
Chris@0 671 echo "\033[31m";
Chris@0 672 }
Chris@0 673
Chris@0 674 echo 'E';
Chris@0 675 } else if ($warnings > 0) {
Chris@0 676 if ($useColors === true) {
Chris@0 677 echo "\033[33m";
Chris@0 678 }
Chris@0 679
Chris@0 680 echo 'W';
Chris@0 681 } else {
Chris@0 682 echo '.';
Chris@0 683 }
Chris@0 684
Chris@0 685 if ($useColors === true) {
Chris@0 686 echo "\033[0m";
Chris@0 687 }
Chris@0 688 }//end if
Chris@0 689
Chris@0 690 $dots++;
Chris@0 691 if ($dots === 60) {
Chris@0 692 $padding = ($maxLength - strlen($numProcessed));
Chris@0 693 echo str_repeat(' ', $padding);
Chris@0 694 $percent = round(($numProcessed / $numFiles) * 100);
Chris@0 695 echo " $numProcessed / $numFiles ($percent%)".PHP_EOL;
Chris@0 696 $dots = 0;
Chris@0 697 }
Chris@0 698 }//end foreach
Chris@0 699
Chris@0 700 if (PHP_CODESNIFFER_VERBOSITY === 0
Chris@0 701 && PHP_CODESNIFFER_INTERACTIVE === false
Chris@0 702 && $showProgress === true
Chris@0 703 ) {
Chris@0 704 echo PHP_EOL.PHP_EOL;
Chris@0 705 }
Chris@0 706
Chris@0 707 }//end processFiles()
Chris@0 708
Chris@0 709
Chris@0 710 /**
Chris@0 711 * Processes a single ruleset and returns a list of the sniffs it represents.
Chris@0 712 *
Chris@0 713 * Rules founds within the ruleset are processed immediately, but sniff classes
Chris@0 714 * are not registered by this method.
Chris@0 715 *
Chris@0 716 * @param string $rulesetPath The path to a ruleset XML file.
Chris@0 717 * @param int $depth How many nested processing steps we are in. This
Chris@0 718 * is only used for debug output.
Chris@0 719 *
Chris@0 720 * @return array
Chris@0 721 * @throws PHP_CodeSniffer_Exception If the ruleset path is invalid.
Chris@0 722 */
Chris@0 723 public function processRuleset($rulesetPath, $depth=0)
Chris@0 724 {
Chris@0 725 $rulesetPath = self::realpath($rulesetPath);
Chris@0 726 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 727 echo str_repeat("\t", $depth);
Chris@0 728 echo "Processing ruleset $rulesetPath".PHP_EOL;
Chris@0 729 }
Chris@0 730
Chris@0 731 $ruleset = simplexml_load_string(file_get_contents($rulesetPath));
Chris@0 732 if ($ruleset === false) {
Chris@0 733 throw new PHP_CodeSniffer_Exception("Ruleset $rulesetPath is not valid");
Chris@0 734 }
Chris@0 735
Chris@0 736 $ownSniffs = array();
Chris@0 737 $includedSniffs = array();
Chris@0 738 $excludedSniffs = array();
Chris@0 739 $cliValues = $this->cli->getCommandLineValues();
Chris@0 740
Chris@0 741 $rulesetDir = dirname($rulesetPath);
Chris@0 742 self::$rulesetDirs[] = $rulesetDir;
Chris@0 743
Chris@0 744 if (is_dir($rulesetDir.DIRECTORY_SEPARATOR.'Sniffs') === true) {
Chris@0 745 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 746 echo str_repeat("\t", $depth);
Chris@0 747 echo "\tAdding sniff files from \"/.../".basename($rulesetDir)."/Sniffs/\" directory".PHP_EOL;
Chris@0 748 }
Chris@0 749
Chris@0 750 $ownSniffs = $this->_expandSniffDirectory($rulesetDir.DIRECTORY_SEPARATOR.'Sniffs', $depth);
Chris@0 751 }
Chris@0 752
Chris@0 753 // Process custom sniff config settings.
Chris@0 754 foreach ($ruleset->{'config'} as $config) {
Chris@0 755 if ($this->_shouldProcessElement($config) === false) {
Chris@0 756 continue;
Chris@0 757 }
Chris@0 758
Chris@0 759 $this->setConfigData((string) $config['name'], (string) $config['value'], true);
Chris@0 760 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 761 echo str_repeat("\t", $depth);
Chris@0 762 echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL;
Chris@0 763 }
Chris@0 764 }
Chris@0 765
Chris@0 766 foreach ($ruleset->rule as $rule) {
Chris@0 767 if (isset($rule['ref']) === false
Chris@0 768 || $this->_shouldProcessElement($rule) === false
Chris@0 769 ) {
Chris@0 770 continue;
Chris@0 771 }
Chris@0 772
Chris@0 773 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 774 echo str_repeat("\t", $depth);
Chris@0 775 echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL;
Chris@0 776 }
Chris@0 777
Chris@0 778 $includedSniffs = array_merge(
Chris@0 779 $includedSniffs,
Chris@0 780 $this->_expandRulesetReference($rule['ref'], $rulesetDir, $depth)
Chris@0 781 );
Chris@0 782
Chris@0 783 if (isset($rule->exclude) === true) {
Chris@0 784 foreach ($rule->exclude as $exclude) {
Chris@0 785 if ($this->_shouldProcessElement($exclude) === false) {
Chris@0 786 continue;
Chris@0 787 }
Chris@0 788
Chris@0 789 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 790 echo str_repeat("\t", $depth);
Chris@0 791 echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL;
Chris@0 792 }
Chris@0 793
Chris@0 794 // Check if a single code is being excluded, which is a shortcut
Chris@0 795 // for setting the severity of the message to 0.
Chris@0 796 $parts = explode('.', $exclude['name']);
Chris@0 797 if (count($parts) === 4) {
Chris@0 798 $this->ruleset[(string) $exclude['name']]['severity'] = 0;
Chris@0 799 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 800 echo str_repeat("\t", $depth);
Chris@0 801 echo "\t\t=> severity set to 0".PHP_EOL;
Chris@0 802 }
Chris@0 803 } else {
Chris@0 804 $excludedSniffs = array_merge(
Chris@0 805 $excludedSniffs,
Chris@0 806 $this->_expandRulesetReference($exclude['name'], $rulesetDir, ($depth + 1))
Chris@0 807 );
Chris@0 808 }
Chris@0 809 }//end foreach
Chris@0 810 }//end if
Chris@0 811
Chris@0 812 $this->_processRule($rule, $depth);
Chris@0 813 }//end foreach
Chris@0 814
Chris@0 815 // Process custom command line arguments.
Chris@0 816 $cliArgs = array();
Chris@0 817 foreach ($ruleset->{'arg'} as $arg) {
Chris@0 818 if ($this->_shouldProcessElement($arg) === false) {
Chris@0 819 continue;
Chris@0 820 }
Chris@0 821
Chris@0 822 if (isset($arg['name']) === true) {
Chris@0 823 $argString = '--'.(string) $arg['name'];
Chris@0 824 if (isset($arg['value']) === true) {
Chris@0 825 $argString .= '='.(string) $arg['value'];
Chris@0 826 }
Chris@0 827 } else {
Chris@0 828 $argString = '-'.(string) $arg['value'];
Chris@0 829 }
Chris@0 830
Chris@0 831 $cliArgs[] = $argString;
Chris@0 832
Chris@0 833 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 834 echo str_repeat("\t", $depth);
Chris@0 835 echo "\t=> set command line value $argString".PHP_EOL;
Chris@0 836 }
Chris@0 837 }//end foreach
Chris@0 838
Chris@0 839 // Set custom php ini values as CLI args.
Chris@0 840 foreach ($ruleset->{'ini'} as $arg) {
Chris@0 841 if ($this->_shouldProcessElement($arg) === false) {
Chris@0 842 continue;
Chris@0 843 }
Chris@0 844
Chris@0 845 if (isset($arg['name']) === false) {
Chris@0 846 continue;
Chris@0 847 }
Chris@0 848
Chris@0 849 $name = (string) $arg['name'];
Chris@0 850 $argString = $name;
Chris@0 851 if (isset($arg['value']) === true) {
Chris@0 852 $value = (string) $arg['value'];
Chris@0 853 $argString .= "=$value";
Chris@0 854 } else {
Chris@0 855 $value = 'true';
Chris@0 856 }
Chris@0 857
Chris@0 858 $cliArgs[] = '-d';
Chris@0 859 $cliArgs[] = $argString;
Chris@0 860
Chris@0 861 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 862 echo str_repeat("\t", $depth);
Chris@0 863 echo "\t=> set PHP ini value $name to $value".PHP_EOL;
Chris@0 864 }
Chris@0 865 }//end foreach
Chris@0 866
Chris@0 867 if (empty($cliValues['files']) === true && $cliValues['stdin'] === null) {
Chris@0 868 // Process hard-coded file paths.
Chris@0 869 foreach ($ruleset->{'file'} as $file) {
Chris@0 870 $file = (string) $file;
Chris@0 871 $cliArgs[] = $file;
Chris@0 872 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 873 echo str_repeat("\t", $depth);
Chris@0 874 echo "\t=> added \"$file\" to the file list".PHP_EOL;
Chris@0 875 }
Chris@0 876 }
Chris@0 877 }
Chris@0 878
Chris@0 879 if (empty($cliArgs) === false) {
Chris@0 880 // Change the directory so all relative paths are worked
Chris@0 881 // out based on the location of the ruleset instead of
Chris@0 882 // the location of the user.
Chris@0 883 $inPhar = self::isPharFile($rulesetDir);
Chris@0 884 if ($inPhar === false) {
Chris@0 885 $currentDir = getcwd();
Chris@0 886 chdir($rulesetDir);
Chris@0 887 }
Chris@0 888
Chris@0 889 $this->cli->setCommandLineValues($cliArgs);
Chris@0 890
Chris@0 891 if ($inPhar === false) {
Chris@0 892 chdir($currentDir);
Chris@0 893 }
Chris@0 894 }
Chris@0 895
Chris@0 896 // Process custom ignore pattern rules.
Chris@0 897 foreach ($ruleset->{'exclude-pattern'} as $pattern) {
Chris@0 898 if ($this->_shouldProcessElement($pattern) === false) {
Chris@0 899 continue;
Chris@0 900 }
Chris@0 901
Chris@0 902 if (isset($pattern['type']) === false) {
Chris@0 903 $pattern['type'] = 'absolute';
Chris@0 904 }
Chris@0 905
Chris@0 906 $this->ignorePatterns[(string) $pattern] = (string) $pattern['type'];
Chris@0 907 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 908 echo str_repeat("\t", $depth);
Chris@0 909 echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
Chris@0 910 }
Chris@0 911 }
Chris@0 912
Chris@0 913 $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs));
Chris@0 914 $excludedSniffs = array_unique($excludedSniffs);
Chris@0 915
Chris@0 916 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 917 $included = count($includedSniffs);
Chris@0 918 $excluded = count($excludedSniffs);
Chris@0 919 echo str_repeat("\t", $depth);
Chris@0 920 echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL;
Chris@0 921 }
Chris@0 922
Chris@0 923 // Merge our own sniff list with our externally included
Chris@0 924 // sniff list, but filter out any excluded sniffs.
Chris@0 925 $files = array();
Chris@0 926 foreach ($includedSniffs as $sniff) {
Chris@0 927 if (in_array($sniff, $excludedSniffs) === true) {
Chris@0 928 continue;
Chris@0 929 } else {
Chris@0 930 $files[] = self::realpath($sniff);
Chris@0 931 }
Chris@0 932 }
Chris@0 933
Chris@0 934 return $files;
Chris@0 935
Chris@0 936 }//end processRuleset()
Chris@0 937
Chris@0 938
Chris@0 939 /**
Chris@0 940 * Expands a directory into a list of sniff files within.
Chris@0 941 *
Chris@0 942 * @param string $directory The path to a directory.
Chris@0 943 * @param int $depth How many nested processing steps we are in. This
Chris@0 944 * is only used for debug output.
Chris@0 945 *
Chris@0 946 * @return array
Chris@0 947 */
Chris@0 948 private function _expandSniffDirectory($directory, $depth=0)
Chris@0 949 {
Chris@0 950 $sniffs = array();
Chris@0 951
Chris@0 952 if (defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') === true) {
Chris@0 953 $rdi = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
Chris@0 954 } else {
Chris@0 955 $rdi = new RecursiveDirectoryIterator($directory);
Chris@0 956 }
Chris@0 957
Chris@0 958 $di = new RecursiveIteratorIterator($rdi, 0, RecursiveIteratorIterator::CATCH_GET_CHILD);
Chris@0 959
Chris@0 960 $dirLen = strlen($directory);
Chris@0 961
Chris@0 962 foreach ($di as $file) {
Chris@0 963 $filename = $file->getFilename();
Chris@0 964
Chris@0 965 // Skip hidden files.
Chris@0 966 if (substr($filename, 0, 1) === '.') {
Chris@0 967 continue;
Chris@0 968 }
Chris@0 969
Chris@0 970 // We are only interested in PHP and sniff files.
Chris@0 971 $fileParts = explode('.', $filename);
Chris@0 972 if (array_pop($fileParts) !== 'php') {
Chris@0 973 continue;
Chris@0 974 }
Chris@0 975
Chris@0 976 $basename = basename($filename, '.php');
Chris@0 977 if (substr($basename, -5) !== 'Sniff') {
Chris@0 978 continue;
Chris@0 979 }
Chris@0 980
Chris@0 981 $path = $file->getPathname();
Chris@0 982
Chris@0 983 // Skip files in hidden directories within the Sniffs directory of this
Chris@0 984 // standard. We use the offset with strpos() to allow hidden directories
Chris@0 985 // before, valid example:
Chris@0 986 // /home/foo/.composer/vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/...
Chris@0 987 if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) {
Chris@0 988 continue;
Chris@0 989 }
Chris@0 990
Chris@0 991 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 992 echo str_repeat("\t", $depth);
Chris@0 993 echo "\t\t=> $path".PHP_EOL;
Chris@0 994 }
Chris@0 995
Chris@0 996 $sniffs[] = $path;
Chris@0 997 }//end foreach
Chris@0 998
Chris@0 999 return $sniffs;
Chris@0 1000
Chris@0 1001 }//end _expandSniffDirectory()
Chris@0 1002
Chris@0 1003
Chris@0 1004 /**
Chris@0 1005 * Expands a ruleset reference into a list of sniff files.
Chris@0 1006 *
Chris@0 1007 * @param string $ref The reference from the ruleset XML file.
Chris@0 1008 * @param string $rulesetDir The directory of the ruleset XML file, used to
Chris@0 1009 * evaluate relative paths.
Chris@0 1010 * @param int $depth How many nested processing steps we are in. This
Chris@0 1011 * is only used for debug output.
Chris@0 1012 *
Chris@0 1013 * @return array
Chris@0 1014 * @throws PHP_CodeSniffer_Exception If the reference is invalid.
Chris@0 1015 */
Chris@0 1016 private function _expandRulesetReference($ref, $rulesetDir, $depth=0)
Chris@0 1017 {
Chris@0 1018 // Ignore internal sniffs codes as they are used to only
Chris@0 1019 // hide and change internal messages.
Chris@0 1020 if (substr($ref, 0, 9) === 'Internal.') {
Chris@0 1021 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1022 echo str_repeat("\t", $depth);
Chris@0 1023 echo "\t\t* ignoring internal sniff code *".PHP_EOL;
Chris@0 1024 }
Chris@0 1025
Chris@0 1026 return array();
Chris@0 1027 }
Chris@0 1028
Chris@0 1029 // As sniffs can't begin with a full stop, assume references in
Chris@0 1030 // this format are relative paths and attempt to convert them
Chris@0 1031 // to absolute paths. If this fails, let the reference run through
Chris@0 1032 // the normal checks and have it fail as normal.
Chris@0 1033 if (substr($ref, 0, 1) === '.') {
Chris@0 1034 $realpath = self::realpath($rulesetDir.'/'.$ref);
Chris@0 1035 if ($realpath !== false) {
Chris@0 1036 $ref = $realpath;
Chris@0 1037 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1038 echo str_repeat("\t", $depth);
Chris@0 1039 echo "\t\t=> $ref".PHP_EOL;
Chris@0 1040 }
Chris@0 1041 }
Chris@0 1042 }
Chris@0 1043
Chris@0 1044 // As sniffs can't begin with a tilde, assume references in
Chris@0 1045 // this format at relative to the user's home directory.
Chris@0 1046 if (substr($ref, 0, 2) === '~/') {
Chris@0 1047 $realpath = self::realpath($ref);
Chris@0 1048 if ($realpath !== false) {
Chris@0 1049 $ref = $realpath;
Chris@0 1050 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1051 echo str_repeat("\t", $depth);
Chris@0 1052 echo "\t\t=> $ref".PHP_EOL;
Chris@0 1053 }
Chris@0 1054 }
Chris@0 1055 }
Chris@0 1056
Chris@0 1057 if (is_file($ref) === true) {
Chris@0 1058 if (substr($ref, -9) === 'Sniff.php') {
Chris@0 1059 // A single external sniff.
Chris@0 1060 self::$rulesetDirs[] = dirname(dirname(dirname($ref)));
Chris@0 1061 return array($ref);
Chris@0 1062 }
Chris@0 1063 } else {
Chris@0 1064 // See if this is a whole standard being referenced.
Chris@0 1065 $path = $this->getInstalledStandardPath($ref);
Chris@0 1066 if (self::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) {
Chris@0 1067 // If the ruleset exists inside the phar file, use it.
Chris@0 1068 if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
Chris@0 1069 $path = $path.DIRECTORY_SEPARATOR.'ruleset.xml';
Chris@0 1070 } else {
Chris@0 1071 $path = null;
Chris@0 1072 }
Chris@0 1073 }
Chris@0 1074
Chris@0 1075 if ($path !== null) {
Chris@0 1076 $ref = $path;
Chris@0 1077 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1078 echo str_repeat("\t", $depth);
Chris@0 1079 echo "\t\t=> $ref".PHP_EOL;
Chris@0 1080 }
Chris@0 1081 } else if (is_dir($ref) === false) {
Chris@0 1082 // Work out the sniff path.
Chris@0 1083 $sepPos = strpos($ref, DIRECTORY_SEPARATOR);
Chris@0 1084 if ($sepPos !== false) {
Chris@0 1085 $stdName = substr($ref, 0, $sepPos);
Chris@0 1086 $path = substr($ref, $sepPos);
Chris@0 1087 } else {
Chris@0 1088 $parts = explode('.', $ref);
Chris@0 1089 $stdName = $parts[0];
Chris@0 1090 if (count($parts) === 1) {
Chris@0 1091 // A whole standard?
Chris@0 1092 $path = '';
Chris@0 1093 } else if (count($parts) === 2) {
Chris@0 1094 // A directory of sniffs?
Chris@0 1095 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1];
Chris@0 1096 } else {
Chris@0 1097 // A single sniff?
Chris@0 1098 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php';
Chris@0 1099 }
Chris@0 1100 }
Chris@0 1101
Chris@0 1102 $newRef = false;
Chris@0 1103 $stdPath = $this->getInstalledStandardPath($stdName);
Chris@0 1104 if ($stdPath !== null && $path !== '') {
Chris@0 1105 if (self::isPharFile($stdPath) === true
Chris@0 1106 && strpos($stdPath, 'ruleset.xml') === false
Chris@0 1107 ) {
Chris@0 1108 // Phar files can only return the directory,
Chris@0 1109 // since ruleset can be omitted if building one standard.
Chris@0 1110 $newRef = self::realpath($stdPath.$path);
Chris@0 1111 } else {
Chris@0 1112 $newRef = self::realpath(dirname($stdPath).$path);
Chris@0 1113 }
Chris@0 1114 }
Chris@0 1115
Chris@0 1116 if ($newRef === false) {
Chris@0 1117 // The sniff is not locally installed, so check if it is being
Chris@0 1118 // referenced as a remote sniff outside the install. We do this
Chris@0 1119 // by looking through all directories where we have found ruleset
Chris@0 1120 // files before, looking for ones for this particular standard,
Chris@0 1121 // and seeing if it is in there.
Chris@0 1122 foreach (self::$rulesetDirs as $dir) {
Chris@0 1123 if (strtolower(basename($dir)) !== strtolower($stdName)) {
Chris@0 1124 continue;
Chris@0 1125 }
Chris@0 1126
Chris@0 1127 $newRef = self::realpath($dir.$path);
Chris@0 1128
Chris@0 1129 if ($newRef !== false) {
Chris@0 1130 $ref = $newRef;
Chris@0 1131 }
Chris@0 1132 }
Chris@0 1133 } else {
Chris@0 1134 $ref = $newRef;
Chris@0 1135 }
Chris@0 1136
Chris@0 1137 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1138 echo str_repeat("\t", $depth);
Chris@0 1139 echo "\t\t=> $ref".PHP_EOL;
Chris@0 1140 }
Chris@0 1141 }//end if
Chris@0 1142 }//end if
Chris@0 1143
Chris@0 1144 if (is_dir($ref) === true) {
Chris@0 1145 if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
Chris@0 1146 // We are referencing an external coding standard.
Chris@0 1147 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1148 echo str_repeat("\t", $depth);
Chris@0 1149 echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL;
Chris@0 1150 }
Chris@0 1151
Chris@0 1152 return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2));
Chris@0 1153 } else {
Chris@0 1154 // We are referencing a whole directory of sniffs.
Chris@0 1155 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1156 echo str_repeat("\t", $depth);
Chris@0 1157 echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL;
Chris@0 1158 echo str_repeat("\t", $depth);
Chris@0 1159 echo "\t\tAdding sniff files from directory".PHP_EOL;
Chris@0 1160 }
Chris@0 1161
Chris@0 1162 return $this->_expandSniffDirectory($ref, ($depth + 1));
Chris@0 1163 }
Chris@0 1164 } else {
Chris@0 1165 if (is_file($ref) === false) {
Chris@0 1166 $error = "Referenced sniff \"$ref\" does not exist";
Chris@0 1167 throw new PHP_CodeSniffer_Exception($error);
Chris@0 1168 }
Chris@0 1169
Chris@0 1170 if (substr($ref, -9) === 'Sniff.php') {
Chris@0 1171 // A single sniff.
Chris@0 1172 return array($ref);
Chris@0 1173 } else {
Chris@0 1174 // Assume an external ruleset.xml file.
Chris@0 1175 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1176 echo str_repeat("\t", $depth);
Chris@0 1177 echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL;
Chris@0 1178 }
Chris@0 1179
Chris@0 1180 return $this->processRuleset($ref, ($depth + 2));
Chris@0 1181 }
Chris@0 1182 }//end if
Chris@0 1183
Chris@0 1184 }//end _expandRulesetReference()
Chris@0 1185
Chris@0 1186
Chris@0 1187 /**
Chris@0 1188 * Processes a rule from a ruleset XML file, overriding built-in defaults.
Chris@0 1189 *
Chris@0 1190 * @param SimpleXMLElement $rule The rule object from a ruleset XML file.
Chris@0 1191 * @param int $depth How many nested processing steps we are in.
Chris@0 1192 * This is only used for debug output.
Chris@0 1193 *
Chris@0 1194 * @return void
Chris@0 1195 */
Chris@0 1196 private function _processRule($rule, $depth=0)
Chris@0 1197 {
Chris@0 1198 $code = (string) $rule['ref'];
Chris@0 1199
Chris@0 1200 // Custom severity.
Chris@0 1201 if (isset($rule->severity) === true
Chris@0 1202 && $this->_shouldProcessElement($rule->severity) === true
Chris@0 1203 ) {
Chris@0 1204 if (isset($this->ruleset[$code]) === false) {
Chris@0 1205 $this->ruleset[$code] = array();
Chris@0 1206 }
Chris@0 1207
Chris@0 1208 $this->ruleset[$code]['severity'] = (int) $rule->severity;
Chris@0 1209 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1210 echo str_repeat("\t", $depth);
Chris@0 1211 echo "\t\t=> severity set to ".(int) $rule->severity.PHP_EOL;
Chris@0 1212 }
Chris@0 1213 }
Chris@0 1214
Chris@0 1215 // Custom message type.
Chris@0 1216 if (isset($rule->type) === true
Chris@0 1217 && $this->_shouldProcessElement($rule->type) === true
Chris@0 1218 ) {
Chris@0 1219 if (isset($this->ruleset[$code]) === false) {
Chris@0 1220 $this->ruleset[$code] = array();
Chris@0 1221 }
Chris@0 1222
Chris@0 1223 $this->ruleset[$code]['type'] = (string) $rule->type;
Chris@0 1224 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1225 echo str_repeat("\t", $depth);
Chris@0 1226 echo "\t\t=> message type set to ".(string) $rule->type.PHP_EOL;
Chris@0 1227 }
Chris@0 1228 }
Chris@0 1229
Chris@0 1230 // Custom message.
Chris@0 1231 if (isset($rule->message) === true
Chris@0 1232 && $this->_shouldProcessElement($rule->message) === true
Chris@0 1233 ) {
Chris@0 1234 if (isset($this->ruleset[$code]) === false) {
Chris@0 1235 $this->ruleset[$code] = array();
Chris@0 1236 }
Chris@0 1237
Chris@0 1238 $this->ruleset[$code]['message'] = (string) $rule->message;
Chris@0 1239 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1240 echo str_repeat("\t", $depth);
Chris@0 1241 echo "\t\t=> message set to ".(string) $rule->message.PHP_EOL;
Chris@0 1242 }
Chris@0 1243 }
Chris@0 1244
Chris@0 1245 // Custom properties.
Chris@0 1246 if (isset($rule->properties) === true
Chris@0 1247 && $this->_shouldProcessElement($rule->properties) === true
Chris@0 1248 ) {
Chris@0 1249 foreach ($rule->properties->property as $prop) {
Chris@0 1250 if ($this->_shouldProcessElement($prop) === false) {
Chris@0 1251 continue;
Chris@0 1252 }
Chris@0 1253
Chris@0 1254 if (isset($this->ruleset[$code]) === false) {
Chris@0 1255 $this->ruleset[$code] = array(
Chris@0 1256 'properties' => array(),
Chris@0 1257 );
Chris@0 1258 } else if (isset($this->ruleset[$code]['properties']) === false) {
Chris@0 1259 $this->ruleset[$code]['properties'] = array();
Chris@0 1260 }
Chris@0 1261
Chris@0 1262 $name = (string) $prop['name'];
Chris@0 1263 if (isset($prop['type']) === true
Chris@0 1264 && (string) $prop['type'] === 'array'
Chris@0 1265 ) {
Chris@0 1266 $value = (string) $prop['value'];
Chris@0 1267 $values = array();
Chris@0 1268 foreach (explode(',', $value) as $val) {
Chris@0 1269 $v = '';
Chris@0 1270
Chris@0 1271 list($k,$v) = explode('=>', $val.'=>');
Chris@0 1272 if ($v !== '') {
Chris@0 1273 $values[$k] = $v;
Chris@0 1274 } else {
Chris@0 1275 $values[] = $k;
Chris@0 1276 }
Chris@0 1277 }
Chris@0 1278
Chris@0 1279 $this->ruleset[$code]['properties'][$name] = $values;
Chris@0 1280 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1281 echo str_repeat("\t", $depth);
Chris@0 1282 echo "\t\t=> array property \"$name\" set to \"$value\"".PHP_EOL;
Chris@0 1283 }
Chris@0 1284 } else {
Chris@0 1285 $this->ruleset[$code]['properties'][$name] = (string) $prop['value'];
Chris@0 1286 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1287 echo str_repeat("\t", $depth);
Chris@0 1288 echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"'.PHP_EOL;
Chris@0 1289 }
Chris@0 1290 }//end if
Chris@0 1291 }//end foreach
Chris@0 1292 }//end if
Chris@0 1293
Chris@0 1294 // Ignore patterns.
Chris@0 1295 foreach ($rule->{'exclude-pattern'} as $pattern) {
Chris@0 1296 if ($this->_shouldProcessElement($pattern) === false) {
Chris@0 1297 continue;
Chris@0 1298 }
Chris@0 1299
Chris@0 1300 if (isset($this->ignorePatterns[$code]) === false) {
Chris@0 1301 $this->ignorePatterns[$code] = array();
Chris@0 1302 }
Chris@0 1303
Chris@0 1304 if (isset($pattern['type']) === false) {
Chris@0 1305 $pattern['type'] = 'absolute';
Chris@0 1306 }
Chris@0 1307
Chris@0 1308 $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type'];
Chris@0 1309 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1310 echo str_repeat("\t", $depth);
Chris@0 1311 echo "\t\t=> added sniff-specific ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
Chris@0 1312 }
Chris@0 1313 }
Chris@0 1314
Chris@0 1315 }//end _processRule()
Chris@0 1316
Chris@0 1317
Chris@0 1318 /**
Chris@0 1319 * Determine if an element should be processed or ignored.
Chris@0 1320 *
Chris@0 1321 * @param SimpleXMLElement $element An object from a ruleset XML file.
Chris@0 1322 * @param int $depth How many nested processing steps we are in.
Chris@0 1323 * This is only used for debug output.
Chris@0 1324 *
Chris@0 1325 * @return bool
Chris@0 1326 */
Chris@0 1327 private function _shouldProcessElement($element, $depth=0)
Chris@0 1328 {
Chris@0 1329 if (isset($element['phpcbf-only']) === false
Chris@0 1330 && isset($element['phpcs-only']) === false
Chris@0 1331 ) {
Chris@0 1332 // No exceptions are being made.
Chris@0 1333 return true;
Chris@0 1334 }
Chris@0 1335
Chris@0 1336 if (PHP_CODESNIFFER_CBF === true
Chris@0 1337 && isset($element['phpcbf-only']) === true
Chris@0 1338 && (string) $element['phpcbf-only'] === 'true'
Chris@0 1339 ) {
Chris@0 1340 return true;
Chris@0 1341 }
Chris@0 1342
Chris@0 1343 if (PHP_CODESNIFFER_CBF === false
Chris@0 1344 && isset($element['phpcs-only']) === true
Chris@0 1345 && (string) $element['phpcs-only'] === 'true'
Chris@0 1346 ) {
Chris@0 1347 return true;
Chris@0 1348 }
Chris@0 1349
Chris@0 1350 return false;
Chris@0 1351
Chris@0 1352 }//end _shouldProcessElement()
Chris@0 1353
Chris@0 1354
Chris@0 1355 /**
Chris@0 1356 * Loads and stores sniffs objects used for sniffing files.
Chris@0 1357 *
Chris@0 1358 * @param array $files Paths to the sniff files to register.
Chris@0 1359 * @param array $restrictions The sniff class names to restrict the allowed
Chris@0 1360 * listeners to.
Chris@0 1361 * @param array $exclusions The sniff class names to exclude from the
Chris@0 1362 * listeners list.
Chris@0 1363 *
Chris@0 1364 * @return void
Chris@0 1365 * @throws PHP_CodeSniffer_Exception If a sniff file path is invalid.
Chris@0 1366 */
Chris@0 1367 public function registerSniffs($files, $restrictions, $exclusions)
Chris@0 1368 {
Chris@0 1369 $listeners = array();
Chris@0 1370
Chris@0 1371 foreach ($files as $file) {
Chris@0 1372 // Work out where the position of /StandardName/Sniffs/... is
Chris@0 1373 // so we can determine what the class will be called.
Chris@0 1374 $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
Chris@0 1375 if ($sniffPos === false) {
Chris@0 1376 continue;
Chris@0 1377 }
Chris@0 1378
Chris@0 1379 $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
Chris@0 1380 if ($slashPos === false) {
Chris@0 1381 continue;
Chris@0 1382 }
Chris@0 1383
Chris@0 1384 $className = substr($file, ($slashPos + 1));
Chris@0 1385
Chris@0 1386 if (substr_count($className, DIRECTORY_SEPARATOR) !== 3) {
Chris@0 1387 throw new PHP_CodeSniffer_Exception("Sniff file $className is not valid; sniff files must be located in a .../StandardName/Sniffs/CategoryName/ directory");
Chris@0 1388 }
Chris@0 1389
Chris@0 1390 $className = substr($className, 0, -4);
Chris@0 1391 $className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
Chris@0 1392
Chris@0 1393 // If they have specified a list of sniffs to restrict to, check
Chris@0 1394 // to see if this sniff is allowed.
Chris@0 1395 if (empty($restrictions) === false
Chris@0 1396 && in_array(strtolower($className), $restrictions) === false
Chris@0 1397 ) {
Chris@0 1398 continue;
Chris@0 1399 }
Chris@0 1400
Chris@0 1401 // If they have specified a list of sniffs to exclude, check
Chris@0 1402 // to see if this sniff is allowed.
Chris@0 1403 if (empty($exclusions) === false
Chris@0 1404 && in_array(strtolower($className), $exclusions) === true
Chris@0 1405 ) {
Chris@0 1406 continue;
Chris@0 1407 }
Chris@0 1408
Chris@0 1409 include_once $file;
Chris@0 1410
Chris@0 1411 // Support the use of PHP namespaces. If the class name we included
Chris@0 1412 // contains namespace separators instead of underscores, use this as the
Chris@0 1413 // class name from now on.
Chris@0 1414 $classNameNS = str_replace('_', '\\', $className);
Chris@0 1415 if (class_exists($classNameNS, false) === true) {
Chris@0 1416 $className = $classNameNS;
Chris@0 1417 }
Chris@0 1418
Chris@0 1419 // Skip abstract classes.
Chris@0 1420 $reflection = new ReflectionClass($className);
Chris@0 1421 if ($reflection->isAbstract() === true) {
Chris@0 1422 continue;
Chris@0 1423 }
Chris@0 1424
Chris@0 1425 $listeners[$className] = $className;
Chris@0 1426
Chris@0 1427 if (PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@0 1428 echo "Registered $className".PHP_EOL;
Chris@0 1429 }
Chris@0 1430 }//end foreach
Chris@0 1431
Chris@0 1432 $this->sniffs = $listeners;
Chris@0 1433
Chris@0 1434 }//end registerSniffs()
Chris@0 1435
Chris@0 1436
Chris@0 1437 /**
Chris@0 1438 * Populates the array of PHP_CodeSniffer_Sniff's for this file.
Chris@0 1439 *
Chris@0 1440 * @return void
Chris@0 1441 * @throws PHP_CodeSniffer_Exception If sniff registration fails.
Chris@0 1442 */
Chris@0 1443 public function populateTokenListeners()
Chris@0 1444 {
Chris@0 1445 // Construct a list of listeners indexed by token being listened for.
Chris@0 1446 $this->_tokenListeners = array();
Chris@0 1447
Chris@0 1448 foreach ($this->sniffs as $listenerClass) {
Chris@0 1449 // Work out the internal code for this sniff. Detect usage of namespace
Chris@0 1450 // separators instead of underscores to support PHP namespaces.
Chris@0 1451 if (strstr($listenerClass, '\\') === false) {
Chris@0 1452 $parts = explode('_', $listenerClass);
Chris@0 1453 } else {
Chris@0 1454 $parts = explode('\\', $listenerClass);
Chris@0 1455 }
Chris@0 1456
Chris@0 1457 $code = $parts[0].'.'.$parts[2].'.'.$parts[3];
Chris@0 1458 $code = substr($code, 0, -5);
Chris@0 1459
Chris@0 1460 $this->listeners[$listenerClass] = new $listenerClass();
Chris@0 1461 $this->sniffCodes[$code] = $listenerClass;
Chris@0 1462
Chris@0 1463 // Set custom properties.
Chris@0 1464 if (isset($this->ruleset[$code]['properties']) === true) {
Chris@0 1465 foreach ($this->ruleset[$code]['properties'] as $name => $value) {
Chris@0 1466 $this->setSniffProperty($listenerClass, $name, $value);
Chris@0 1467 }
Chris@0 1468 }
Chris@0 1469
Chris@0 1470 $tokenizers = array();
Chris@0 1471 $vars = get_class_vars($listenerClass);
Chris@0 1472 if (isset($vars['supportedTokenizers']) === true) {
Chris@0 1473 foreach ($vars['supportedTokenizers'] as $tokenizer) {
Chris@0 1474 $tokenizers[$tokenizer] = $tokenizer;
Chris@0 1475 }
Chris@0 1476 } else {
Chris@0 1477 $tokenizers = array('PHP' => 'PHP');
Chris@0 1478 }
Chris@0 1479
Chris@0 1480 $tokens = $this->listeners[$listenerClass]->register();
Chris@0 1481 if (is_array($tokens) === false) {
Chris@0 1482 $msg = "Sniff $listenerClass register() method must return an array";
Chris@0 1483 throw new PHP_CodeSniffer_Exception($msg);
Chris@0 1484 }
Chris@0 1485
Chris@0 1486 $parts = explode('_', str_replace('\\', '_', $listenerClass));
Chris@0 1487 $listenerSource = $parts[0].'.'.$parts[2].'.'.substr($parts[3], 0, -5);
Chris@0 1488 $ignorePatterns = array();
Chris@0 1489 $patterns = $this->getIgnorePatterns($listenerSource);
Chris@0 1490 foreach ($patterns as $pattern => $type) {
Chris@0 1491 // While there is support for a type of each pattern
Chris@0 1492 // (absolute or relative) we don't actually support it here.
Chris@0 1493 $replacements = array(
Chris@0 1494 '\\,' => ',',
Chris@0 1495 '*' => '.*',
Chris@0 1496 );
Chris@0 1497
Chris@0 1498 $ignorePatterns[] = strtr($pattern, $replacements);
Chris@0 1499 }
Chris@0 1500
Chris@0 1501 foreach ($tokens as $token) {
Chris@0 1502 if (isset($this->_tokenListeners[$token]) === false) {
Chris@0 1503 $this->_tokenListeners[$token] = array();
Chris@0 1504 }
Chris@0 1505
Chris@0 1506 if (isset($this->_tokenListeners[$token][$listenerClass]) === false) {
Chris@0 1507 $this->_tokenListeners[$token][$listenerClass] = array(
Chris@0 1508 'class' => $listenerClass,
Chris@0 1509 'source' => $listenerSource,
Chris@0 1510 'tokenizers' => $tokenizers,
Chris@0 1511 'ignore' => $ignorePatterns,
Chris@0 1512 );
Chris@0 1513 }
Chris@0 1514 }
Chris@0 1515 }//end foreach
Chris@0 1516
Chris@0 1517 }//end populateTokenListeners()
Chris@0 1518
Chris@0 1519
Chris@0 1520 /**
Chris@0 1521 * Set a single property for a sniff.
Chris@0 1522 *
Chris@0 1523 * @param string $listenerClass The class name of the sniff.
Chris@0 1524 * @param string $name The name of the property to change.
Chris@0 1525 * @param string $value The new value of the property.
Chris@0 1526 *
Chris@0 1527 * @return void
Chris@0 1528 */
Chris@0 1529 public function setSniffProperty($listenerClass, $name, $value)
Chris@0 1530 {
Chris@0 1531 // Setting a property for a sniff we are not using.
Chris@0 1532 if (isset($this->listeners[$listenerClass]) === false) {
Chris@0 1533 return;
Chris@0 1534 }
Chris@0 1535
Chris@0 1536 $name = trim($name);
Chris@0 1537 if (is_string($value) === true) {
Chris@0 1538 $value = trim($value);
Chris@0 1539 }
Chris@0 1540
Chris@0 1541 // Special case for booleans.
Chris@0 1542 if ($value === 'true') {
Chris@0 1543 $value = true;
Chris@0 1544 } else if ($value === 'false') {
Chris@0 1545 $value = false;
Chris@0 1546 }
Chris@0 1547
Chris@0 1548 $this->listeners[$listenerClass]->$name = $value;
Chris@0 1549
Chris@0 1550 }//end setSniffProperty()
Chris@0 1551
Chris@0 1552
Chris@0 1553 /**
Chris@0 1554 * Get a list of files that will be processed.
Chris@0 1555 *
Chris@0 1556 * If passed directories, this method will find all files within them.
Chris@0 1557 * The method will also perform file extension and ignore pattern filtering.
Chris@0 1558 *
Chris@0 1559 * @param string $paths A list of file or directory paths to process.
Chris@0 1560 * @param boolean $local If true, only process 1 level of files in directories
Chris@0 1561 *
Chris@0 1562 * @return array
Chris@0 1563 * @throws Exception If there was an error opening a directory.
Chris@0 1564 * @see shouldProcessFile()
Chris@0 1565 */
Chris@0 1566 public function getFilesToProcess($paths, $local=false)
Chris@0 1567 {
Chris@0 1568 $files = array();
Chris@0 1569
Chris@0 1570 foreach ($paths as $path) {
Chris@0 1571 if (is_dir($path) === true || self::isPharFile($path) === true) {
Chris@0 1572 if (self::isPharFile($path) === true) {
Chris@0 1573 $path = 'phar://'.$path;
Chris@0 1574 }
Chris@0 1575
Chris@0 1576 if ($local === true) {
Chris@0 1577 $di = new DirectoryIterator($path);
Chris@0 1578 } else {
Chris@0 1579 $di = new RecursiveIteratorIterator(
Chris@0 1580 new RecursiveDirectoryIterator($path),
Chris@0 1581 0,
Chris@0 1582 RecursiveIteratorIterator::CATCH_GET_CHILD
Chris@0 1583 );
Chris@0 1584 }
Chris@0 1585
Chris@0 1586 foreach ($di as $file) {
Chris@0 1587 // Check if the file exists after all symlinks are resolved.
Chris@0 1588 $filePath = self::realpath($file->getPathname());
Chris@0 1589 if ($filePath === false) {
Chris@0 1590 continue;
Chris@0 1591 }
Chris@0 1592
Chris@0 1593 if (is_dir($filePath) === true) {
Chris@0 1594 continue;
Chris@0 1595 }
Chris@0 1596
Chris@0 1597 if ($this->shouldProcessFile($file->getPathname(), $path) === false) {
Chris@0 1598 continue;
Chris@0 1599 }
Chris@0 1600
Chris@0 1601 $files[] = $file->getPathname();
Chris@0 1602 }//end foreach
Chris@0 1603 } else {
Chris@0 1604 if ($this->shouldIgnoreFile($path, dirname($path)) === true) {
Chris@0 1605 continue;
Chris@0 1606 }
Chris@0 1607
Chris@0 1608 $files[] = $path;
Chris@0 1609 }//end if
Chris@0 1610 }//end foreach
Chris@0 1611
Chris@0 1612 return $files;
Chris@0 1613
Chris@0 1614 }//end getFilesToProcess()
Chris@0 1615
Chris@0 1616
Chris@0 1617 /**
Chris@0 1618 * Checks filtering rules to see if a file should be checked.
Chris@0 1619 *
Chris@0 1620 * Checks both file extension filters and path ignore filters.
Chris@0 1621 *
Chris@0 1622 * @param string $path The path to the file being checked.
Chris@0 1623 * @param string $basedir The directory to use for relative path checks.
Chris@0 1624 *
Chris@0 1625 * @return bool
Chris@0 1626 */
Chris@0 1627 public function shouldProcessFile($path, $basedir)
Chris@0 1628 {
Chris@0 1629 // Check that the file's extension is one we are checking.
Chris@0 1630 // We are strict about checking the extension and we don't
Chris@0 1631 // let files through with no extension or that start with a dot.
Chris@0 1632 $fileName = basename($path);
Chris@0 1633 $fileParts = explode('.', $fileName);
Chris@0 1634 if ($fileParts[0] === $fileName || $fileParts[0] === '') {
Chris@0 1635 return false;
Chris@0 1636 }
Chris@0 1637
Chris@0 1638 // Checking multi-part file extensions, so need to create a
Chris@0 1639 // complete extension list and make sure one is allowed.
Chris@0 1640 $extensions = array();
Chris@0 1641 array_shift($fileParts);
Chris@0 1642 foreach ($fileParts as $part) {
Chris@0 1643 $extensions[implode('.', $fileParts)] = 1;
Chris@0 1644 array_shift($fileParts);
Chris@0 1645 }
Chris@0 1646
Chris@0 1647 $matches = array_intersect_key($extensions, $this->allowedFileExtensions);
Chris@0 1648 if (empty($matches) === true) {
Chris@0 1649 return false;
Chris@0 1650 }
Chris@0 1651
Chris@0 1652 // If the file's path matches one of our ignore patterns, skip it.
Chris@0 1653 if ($this->shouldIgnoreFile($path, $basedir) === true) {
Chris@0 1654 return false;
Chris@0 1655 }
Chris@0 1656
Chris@0 1657 return true;
Chris@0 1658
Chris@0 1659 }//end shouldProcessFile()
Chris@0 1660
Chris@0 1661
Chris@0 1662 /**
Chris@0 1663 * Checks filtering rules to see if a file should be ignored.
Chris@0 1664 *
Chris@0 1665 * @param string $path The path to the file being checked.
Chris@0 1666 * @param string $basedir The directory to use for relative path checks.
Chris@0 1667 *
Chris@0 1668 * @return bool
Chris@0 1669 */
Chris@0 1670 public function shouldIgnoreFile($path, $basedir)
Chris@0 1671 {
Chris@0 1672 $relativePath = $path;
Chris@0 1673 if (strpos($path, $basedir) === 0) {
Chris@0 1674 // The +1 cuts off the directory separator as well.
Chris@0 1675 $relativePath = substr($path, (strlen($basedir) + 1));
Chris@0 1676 }
Chris@0 1677
Chris@0 1678 foreach ($this->ignorePatterns as $pattern => $type) {
Chris@0 1679 if (is_array($type) === true) {
Chris@0 1680 // A sniff specific ignore pattern.
Chris@0 1681 continue;
Chris@0 1682 }
Chris@0 1683
Chris@0 1684 // Maintains backwards compatibility in case the ignore pattern does
Chris@0 1685 // not have a relative/absolute value.
Chris@0 1686 if (is_int($pattern) === true) {
Chris@0 1687 $pattern = $type;
Chris@0 1688 $type = 'absolute';
Chris@0 1689 }
Chris@0 1690
Chris@0 1691 $replacements = array(
Chris@0 1692 '\\,' => ',',
Chris@0 1693 '*' => '.*',
Chris@0 1694 );
Chris@0 1695
Chris@0 1696 // We assume a / directory separator, as do the exclude rules
Chris@0 1697 // most developers write, so we need a special case for any system
Chris@0 1698 // that is different.
Chris@0 1699 if (DIRECTORY_SEPARATOR === '\\') {
Chris@0 1700 $replacements['/'] = '\\\\';
Chris@0 1701 }
Chris@0 1702
Chris@0 1703 $pattern = strtr($pattern, $replacements);
Chris@0 1704
Chris@0 1705 if ($type === 'relative') {
Chris@0 1706 $testPath = $relativePath;
Chris@0 1707 } else {
Chris@0 1708 $testPath = $path;
Chris@0 1709 }
Chris@0 1710
Chris@0 1711 $pattern = '`'.$pattern.'`i';
Chris@0 1712 if (preg_match($pattern, $testPath) === 1) {
Chris@0 1713 return true;
Chris@0 1714 }
Chris@0 1715 }//end foreach
Chris@0 1716
Chris@0 1717 return false;
Chris@0 1718
Chris@0 1719 }//end shouldIgnoreFile()
Chris@0 1720
Chris@0 1721
Chris@0 1722 /**
Chris@0 1723 * Run the code sniffs over a single given file.
Chris@0 1724 *
Chris@0 1725 * Processes the file and runs the PHP_CodeSniffer sniffs to verify that it
Chris@0 1726 * conforms with the standard. Returns the processed file object, or NULL
Chris@0 1727 * if no file was processed due to error.
Chris@0 1728 *
Chris@0 1729 * @param string $file The file to process.
Chris@0 1730 * @param string $contents The contents to parse. If NULL, the content
Chris@0 1731 * is taken from the file system.
Chris@0 1732 *
Chris@0 1733 * @return PHP_CodeSniffer_File
Chris@0 1734 * @throws PHP_CodeSniffer_Exception If the file could not be processed.
Chris@0 1735 * @see _processFile()
Chris@0 1736 */
Chris@0 1737 public function processFile($file, $contents=null)
Chris@0 1738 {
Chris@0 1739 if ($contents === null && file_exists($file) === false) {
Chris@0 1740 throw new PHP_CodeSniffer_Exception("Source file $file does not exist");
Chris@0 1741 }
Chris@0 1742
Chris@0 1743 $filePath = self::realpath($file);
Chris@0 1744 if ($filePath === false) {
Chris@0 1745 $filePath = $file;
Chris@0 1746 }
Chris@0 1747
Chris@0 1748 // Before we go and spend time tokenizing this file, just check
Chris@0 1749 // to see if there is a tag up top to indicate that the whole
Chris@0 1750 // file should be ignored. It must be on one of the first two lines.
Chris@0 1751 $firstContent = $contents;
Chris@0 1752 if ($contents === null && is_readable($filePath) === true) {
Chris@0 1753 $handle = fopen($filePath, 'r');
Chris@0 1754 stream_set_blocking($handle, true);
Chris@0 1755 if ($handle !== false) {
Chris@0 1756 $firstContent = fgets($handle);
Chris@0 1757 $firstContent .= fgets($handle);
Chris@0 1758 fclose($handle);
Chris@0 1759
Chris@0 1760 if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false) {
Chris@0 1761 // We are ignoring the whole file.
Chris@0 1762 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@0 1763 echo 'Ignoring '.basename($filePath).PHP_EOL;
Chris@0 1764 }
Chris@0 1765
Chris@0 1766 return null;
Chris@0 1767 }
Chris@0 1768 }
Chris@0 1769 }//end if
Chris@0 1770
Chris@0 1771 try {
Chris@0 1772 $phpcsFile = $this->_processFile($file, $contents);
Chris@0 1773 } catch (Exception $e) {
Chris@0 1774 $trace = $e->getTrace();
Chris@0 1775
Chris@0 1776 $filename = $trace[0]['args'][0];
Chris@0 1777 if (is_object($filename) === true
Chris@0 1778 && get_class($filename) === 'PHP_CodeSniffer_File'
Chris@0 1779 ) {
Chris@0 1780 $filename = $filename->getFilename();
Chris@0 1781 } else if (is_numeric($filename) === true) {
Chris@0 1782 // See if we can find the PHP_CodeSniffer_File object.
Chris@0 1783 foreach ($trace as $data) {
Chris@0 1784 if (isset($data['args'][0]) === true
Chris@0 1785 && ($data['args'][0] instanceof PHP_CodeSniffer_File) === true
Chris@0 1786 ) {
Chris@0 1787 $filename = $data['args'][0]->getFilename();
Chris@0 1788 }
Chris@0 1789 }
Chris@0 1790 } else if (is_string($filename) === false) {
Chris@0 1791 $filename = (string) $filename;
Chris@0 1792 }
Chris@0 1793
Chris@0 1794 $errorMessage = '"'.$e->getMessage().'" at '.$e->getFile().':'.$e->getLine();
Chris@0 1795 $error = "An error occurred during processing; checking has been aborted. The error message was: $errorMessage";
Chris@0 1796
Chris@0 1797 $phpcsFile = new PHP_CodeSniffer_File(
Chris@0 1798 $filename,
Chris@0 1799 $this->_tokenListeners,
Chris@0 1800 $this->ruleset,
Chris@0 1801 $this
Chris@0 1802 );
Chris@0 1803
Chris@2 1804 $phpcsFile->addError($error, null, 'Internal.Exception');
Chris@0 1805 }//end try
Chris@0 1806
Chris@0 1807 $cliValues = $this->cli->getCommandLineValues();
Chris@0 1808
Chris@0 1809 if (PHP_CODESNIFFER_INTERACTIVE === false) {
Chris@0 1810 // Cache the report data for this file so we can unset it to save memory.
Chris@0 1811 $this->reporting->cacheFileReport($phpcsFile, $cliValues);
Chris@0 1812 $phpcsFile->cleanUp();
Chris@0 1813 return $phpcsFile;
Chris@0 1814 }
Chris@0 1815
Chris@0 1816 /*
Chris@0 1817 Running interactively.
Chris@0 1818 Print the error report for the current file and then wait for user input.
Chris@0 1819 */
Chris@0 1820
Chris@0 1821 // Get current violations and then clear the list to make sure
Chris@0 1822 // we only print violations for a single file each time.
Chris@0 1823 $numErrors = null;
Chris@0 1824 while ($numErrors !== 0) {
Chris@0 1825 $numErrors = ($phpcsFile->getErrorCount() + $phpcsFile->getWarningCount());
Chris@0 1826 if ($numErrors === 0) {
Chris@0 1827 continue;
Chris@0 1828 }
Chris@0 1829
Chris@0 1830 $reportClass = $this->reporting->factory('full');
Chris@0 1831 $reportData = $this->reporting->prepareFileReport($phpcsFile);
Chris@0 1832 $reportClass->generateFileReport($reportData, $phpcsFile, $cliValues['showSources'], $cliValues['reportWidth']);
Chris@0 1833
Chris@0 1834 echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
Chris@0 1835 $input = fgets(STDIN);
Chris@0 1836 $input = trim($input);
Chris@0 1837
Chris@0 1838 switch ($input) {
Chris@0 1839 case 's':
Chris@0 1840 break(2);
Chris@0 1841 case 'q':
Chris@0 1842 exit(0);
Chris@0 1843 break;
Chris@0 1844 default:
Chris@0 1845 // Repopulate the sniffs because some of them save their state
Chris@0 1846 // and only clear it when the file changes, but we are rechecking
Chris@0 1847 // the same file.
Chris@0 1848 $this->populateTokenListeners();
Chris@0 1849 $phpcsFile = $this->_processFile($file, $contents);
Chris@0 1850 break;
Chris@0 1851 }
Chris@0 1852 }//end while
Chris@0 1853
Chris@0 1854 return $phpcsFile;
Chris@0 1855
Chris@0 1856 }//end processFile()
Chris@0 1857
Chris@0 1858
Chris@0 1859 /**
Chris@0 1860 * Process the sniffs for a single file.
Chris@0 1861 *
Chris@0 1862 * Does raw processing only. No interactive support or error checking.
Chris@0 1863 *
Chris@0 1864 * @param string $file The file to process.
Chris@0 1865 * @param string $contents The contents to parse. If NULL, the content
Chris@0 1866 * is taken from the file system.
Chris@0 1867 *
Chris@0 1868 * @return PHP_CodeSniffer_File
Chris@0 1869 * @see processFile()
Chris@0 1870 */
Chris@0 1871 private function _processFile($file, $contents)
Chris@0 1872 {
Chris@0 1873 $stdin = false;
Chris@0 1874 $cliValues = $this->cli->getCommandLineValues();
Chris@0 1875 if (empty($cliValues['files']) === true) {
Chris@0 1876 $stdin = true;
Chris@0 1877 }
Chris@0 1878
Chris@0 1879 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) {
Chris@0 1880 $startTime = microtime(true);
Chris@0 1881 echo 'Processing '.basename($file).' ';
Chris@0 1882 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@0 1883 echo PHP_EOL;
Chris@0 1884 }
Chris@0 1885 }
Chris@0 1886
Chris@0 1887 $phpcsFile = new PHP_CodeSniffer_File(
Chris@0 1888 $file,
Chris@0 1889 $this->_tokenListeners,
Chris@0 1890 $this->ruleset,
Chris@0 1891 $this
Chris@0 1892 );
Chris@0 1893
Chris@0 1894 $phpcsFile->start($contents);
Chris@0 1895
Chris@0 1896 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) {
Chris@0 1897 $timeTaken = ((microtime(true) - $startTime) * 1000);
Chris@0 1898 if ($timeTaken < 1000) {
Chris@0 1899 $timeTaken = round($timeTaken);
Chris@0 1900 echo "DONE in {$timeTaken}ms";
Chris@0 1901 } else {
Chris@0 1902 $timeTaken = round(($timeTaken / 1000), 2);
Chris@0 1903 echo "DONE in $timeTaken secs";
Chris@0 1904 }
Chris@0 1905
Chris@0 1906 if (PHP_CODESNIFFER_CBF === true) {
Chris@0 1907 $errors = $phpcsFile->getFixableCount();
Chris@0 1908 echo " ($errors fixable violations)".PHP_EOL;
Chris@0 1909 } else {
Chris@0 1910 $errors = $phpcsFile->getErrorCount();
Chris@0 1911 $warnings = $phpcsFile->getWarningCount();
Chris@0 1912 echo " ($errors errors, $warnings warnings)".PHP_EOL;
Chris@0 1913 }
Chris@0 1914 }
Chris@0 1915
Chris@0 1916 return $phpcsFile;
Chris@0 1917
Chris@0 1918 }//end _processFile()
Chris@0 1919
Chris@0 1920
Chris@0 1921 /**
Chris@0 1922 * Generates documentation for a coding standard.
Chris@0 1923 *
Chris@0 1924 * @param string $standard The standard to generate docs for
Chris@0 1925 * @param array $sniffs A list of sniffs to limit the docs to.
Chris@0 1926 * @param string $generator The name of the generator class to use.
Chris@0 1927 *
Chris@0 1928 * @return void
Chris@0 1929 */
Chris@0 1930 public function generateDocs($standard, array $sniffs=array(), $generator='Text')
Chris@0 1931 {
Chris@0 1932 if (class_exists('PHP_CodeSniffer_DocGenerators_'.$generator, true) === false) {
Chris@0 1933 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_DocGenerators_'.$generator.' not found');
Chris@0 1934 }
Chris@0 1935
Chris@0 1936 $class = "PHP_CodeSniffer_DocGenerators_$generator";
Chris@0 1937 $generator = new $class($standard, $sniffs);
Chris@0 1938
Chris@0 1939 $generator->generate();
Chris@0 1940
Chris@0 1941 }//end generateDocs()
Chris@0 1942
Chris@0 1943
Chris@0 1944 /**
Chris@0 1945 * Gets the array of PHP_CodeSniffer_Sniff's.
Chris@0 1946 *
Chris@0 1947 * @return PHP_CodeSniffer_Sniff[]
Chris@0 1948 */
Chris@0 1949 public function getSniffs()
Chris@0 1950 {
Chris@0 1951 return $this->listeners;
Chris@0 1952
Chris@0 1953 }//end getSniffs()
Chris@0 1954
Chris@0 1955
Chris@0 1956 /**
Chris@0 1957 * Gets the array of PHP_CodeSniffer_Sniff's indexed by token type.
Chris@0 1958 *
Chris@0 1959 * @return array
Chris@0 1960 */
Chris@0 1961 public function getTokenSniffs()
Chris@0 1962 {
Chris@0 1963 return $this->_tokenListeners;
Chris@0 1964
Chris@0 1965 }//end getTokenSniffs()
Chris@0 1966
Chris@0 1967
Chris@0 1968 /**
Chris@0 1969 * Returns true if the specified string is in the camel caps format.
Chris@0 1970 *
Chris@0 1971 * @param string $string The string the verify.
Chris@0 1972 * @param boolean $classFormat If true, check to see if the string is in the
Chris@0 1973 * class format. Class format strings must start
Chris@0 1974 * with a capital letter and contain no
Chris@0 1975 * underscores.
Chris@0 1976 * @param boolean $public If true, the first character in the string
Chris@0 1977 * must be an a-z character. If false, the
Chris@0 1978 * character must be an underscore. This
Chris@0 1979 * argument is only applicable if $classFormat
Chris@0 1980 * is false.
Chris@0 1981 * @param boolean $strict If true, the string must not have two capital
Chris@0 1982 * letters next to each other. If false, a
Chris@0 1983 * relaxed camel caps policy is used to allow
Chris@0 1984 * for acronyms.
Chris@0 1985 *
Chris@0 1986 * @return boolean
Chris@0 1987 */
Chris@0 1988 public static function isCamelCaps(
Chris@0 1989 $string,
Chris@0 1990 $classFormat=false,
Chris@0 1991 $public=true,
Chris@0 1992 $strict=true
Chris@0 1993 ) {
Chris@0 1994 // Check the first character first.
Chris@0 1995 if ($classFormat === false) {
Chris@0 1996 $legalFirstChar = '';
Chris@0 1997 if ($public === false) {
Chris@0 1998 $legalFirstChar = '[_]';
Chris@0 1999 }
Chris@0 2000
Chris@0 2001 if ($strict === false) {
Chris@0 2002 // Can either start with a lowercase letter, or multiple uppercase
Chris@0 2003 // in a row, representing an acronym.
Chris@0 2004 $legalFirstChar .= '([A-Z]{2,}|[a-z])';
Chris@0 2005 } else {
Chris@0 2006 $legalFirstChar .= '[a-z]';
Chris@0 2007 }
Chris@0 2008 } else {
Chris@0 2009 $legalFirstChar = '[A-Z]';
Chris@0 2010 }
Chris@0 2011
Chris@0 2012 if (preg_match("/^$legalFirstChar/", $string) === 0) {
Chris@0 2013 return false;
Chris@0 2014 }
Chris@0 2015
Chris@0 2016 // Check that the name only contains legal characters.
Chris@0 2017 $legalChars = 'a-zA-Z0-9';
Chris@0 2018 if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
Chris@0 2019 return false;
Chris@0 2020 }
Chris@0 2021
Chris@0 2022 if ($strict === true) {
Chris@0 2023 // Check that there are not two capital letters next to each other.
Chris@0 2024 $length = strlen($string);
Chris@0 2025 $lastCharWasCaps = $classFormat;
Chris@0 2026
Chris@0 2027 for ($i = 1; $i < $length; $i++) {
Chris@0 2028 $ascii = ord($string{$i});
Chris@0 2029 if ($ascii >= 48 && $ascii <= 57) {
Chris@0 2030 // The character is a number, so it cant be a capital.
Chris@0 2031 $isCaps = false;
Chris@0 2032 } else {
Chris@0 2033 if (strtoupper($string{$i}) === $string{$i}) {
Chris@0 2034 $isCaps = true;
Chris@0 2035 } else {
Chris@0 2036 $isCaps = false;
Chris@0 2037 }
Chris@0 2038 }
Chris@0 2039
Chris@0 2040 if ($isCaps === true && $lastCharWasCaps === true) {
Chris@0 2041 return false;
Chris@0 2042 }
Chris@0 2043
Chris@0 2044 $lastCharWasCaps = $isCaps;
Chris@0 2045 }
Chris@0 2046 }//end if
Chris@0 2047
Chris@0 2048 return true;
Chris@0 2049
Chris@0 2050 }//end isCamelCaps()
Chris@0 2051
Chris@0 2052
Chris@0 2053 /**
Chris@0 2054 * Returns true if the specified string is in the underscore caps format.
Chris@0 2055 *
Chris@0 2056 * @param string $string The string to verify.
Chris@0 2057 *
Chris@0 2058 * @return boolean
Chris@0 2059 */
Chris@0 2060 public static function isUnderscoreName($string)
Chris@0 2061 {
Chris@0 2062 // If there are space in the name, it can't be valid.
Chris@0 2063 if (strpos($string, ' ') !== false) {
Chris@0 2064 return false;
Chris@0 2065 }
Chris@0 2066
Chris@0 2067 $validName = true;
Chris@0 2068 $nameBits = explode('_', $string);
Chris@0 2069
Chris@0 2070 if (preg_match('|^[A-Z]|', $string) === 0) {
Chris@0 2071 // Name does not begin with a capital letter.
Chris@0 2072 $validName = false;
Chris@0 2073 } else {
Chris@0 2074 foreach ($nameBits as $bit) {
Chris@0 2075 if ($bit === '') {
Chris@0 2076 continue;
Chris@0 2077 }
Chris@0 2078
Chris@0 2079 if ($bit{0} !== strtoupper($bit{0})) {
Chris@0 2080 $validName = false;
Chris@0 2081 break;
Chris@0 2082 }
Chris@0 2083 }
Chris@0 2084 }
Chris@0 2085
Chris@0 2086 return $validName;
Chris@0 2087
Chris@0 2088 }//end isUnderscoreName()
Chris@0 2089
Chris@0 2090
Chris@0 2091 /**
Chris@0 2092 * Returns a valid variable type for param/var tag.
Chris@0 2093 *
Chris@0 2094 * If type is not one of the standard type, it must be a custom type.
Chris@0 2095 * Returns the correct type name suggestion if type name is invalid.
Chris@0 2096 *
Chris@0 2097 * @param string $varType The variable type to process.
Chris@0 2098 *
Chris@0 2099 * @return string
Chris@0 2100 */
Chris@0 2101 public static function suggestType($varType)
Chris@0 2102 {
Chris@0 2103 if ($varType === '') {
Chris@0 2104 return '';
Chris@0 2105 }
Chris@0 2106
Chris@0 2107 if (in_array($varType, self::$allowedTypes) === true) {
Chris@0 2108 return $varType;
Chris@0 2109 } else {
Chris@0 2110 $lowerVarType = strtolower($varType);
Chris@0 2111 switch ($lowerVarType) {
Chris@0 2112 case 'bool':
Chris@0 2113 case 'boolean':
Chris@0 2114 return 'boolean';
Chris@0 2115 case 'double':
Chris@0 2116 case 'real':
Chris@0 2117 case 'float':
Chris@0 2118 return 'float';
Chris@0 2119 case 'int':
Chris@0 2120 case 'integer':
Chris@0 2121 return 'integer';
Chris@0 2122 case 'array()':
Chris@0 2123 case 'array':
Chris@0 2124 return 'array';
Chris@0 2125 }//end switch
Chris@0 2126
Chris@0 2127 if (strpos($lowerVarType, 'array(') !== false) {
Chris@0 2128 // Valid array declaration:
Chris@0 2129 // array, array(type), array(type1 => type2).
Chris@0 2130 $matches = array();
Chris@0 2131 $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i';
Chris@0 2132 if (preg_match($pattern, $varType, $matches) !== 0) {
Chris@0 2133 $type1 = '';
Chris@0 2134 if (isset($matches[1]) === true) {
Chris@0 2135 $type1 = $matches[1];
Chris@0 2136 }
Chris@0 2137
Chris@0 2138 $type2 = '';
Chris@0 2139 if (isset($matches[3]) === true) {
Chris@0 2140 $type2 = $matches[3];
Chris@0 2141 }
Chris@0 2142
Chris@0 2143 $type1 = self::suggestType($type1);
Chris@0 2144 $type2 = self::suggestType($type2);
Chris@0 2145 if ($type2 !== '') {
Chris@0 2146 $type2 = ' => '.$type2;
Chris@0 2147 }
Chris@0 2148
Chris@0 2149 return "array($type1$type2)";
Chris@0 2150 } else {
Chris@0 2151 return 'array';
Chris@0 2152 }//end if
Chris@0 2153 } else if (in_array($lowerVarType, self::$allowedTypes) === true) {
Chris@0 2154 // A valid type, but not lower cased.
Chris@0 2155 return $lowerVarType;
Chris@0 2156 } else {
Chris@0 2157 // Must be a custom type name.
Chris@0 2158 return $varType;
Chris@0 2159 }//end if
Chris@0 2160 }//end if
Chris@0 2161
Chris@0 2162 }//end suggestType()
Chris@0 2163
Chris@0 2164
Chris@0 2165 /**
Chris@0 2166 * Prepares token content for output to screen.
Chris@0 2167 *
Chris@0 2168 * Replaces invisible characters so they are visible. On non-Windows
Chris@0 2169 * OSes it will also colour the invisible characters.
Chris@0 2170 *
Chris@0 2171 * @param string $content The content to prepare.
Chris@0 2172 *
Chris@0 2173 * @return string
Chris@0 2174 */
Chris@0 2175 public static function prepareForOutput($content)
Chris@0 2176 {
Chris@0 2177 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
Chris@0 2178 $content = str_replace("\r", '\r', $content);
Chris@0 2179 $content = str_replace("\n", '\n', $content);
Chris@0 2180 $content = str_replace("\t", '\t', $content);
Chris@0 2181 } else {
Chris@0 2182 $content = str_replace("\r", "\033[30;1m\\r\033[0m", $content);
Chris@0 2183 $content = str_replace("\n", "\033[30;1m\\n\033[0m", $content);
Chris@0 2184 $content = str_replace("\t", "\033[30;1m\\t\033[0m", $content);
Chris@0 2185 $content = str_replace(' ', "\033[30;1m·\033[0m", $content);
Chris@0 2186 }
Chris@0 2187
Chris@0 2188 return $content;
Chris@0 2189
Chris@0 2190 }//end prepareForOutput()
Chris@0 2191
Chris@0 2192
Chris@0 2193 /**
Chris@0 2194 * Get a list paths where standards are installed.
Chris@0 2195 *
Chris@0 2196 * @return array
Chris@0 2197 */
Chris@0 2198 public static function getInstalledStandardPaths()
Chris@0 2199 {
Chris@0 2200 $installedPaths = array(dirname(__FILE__).DIRECTORY_SEPARATOR.'CodeSniffer'.DIRECTORY_SEPARATOR.'Standards');
Chris@0 2201 $configPaths = PHP_CodeSniffer::getConfigData('installed_paths');
Chris@0 2202 if ($configPaths !== null) {
Chris@0 2203 $installedPaths = array_merge($installedPaths, explode(',', $configPaths));
Chris@0 2204 }
Chris@0 2205
Chris@0 2206 $resolvedInstalledPaths = array();
Chris@0 2207 foreach ($installedPaths as $installedPath) {
Chris@0 2208 if (substr($installedPath, 0, 1) === '.') {
Chris@0 2209 $installedPath = dirname(__FILE__).DIRECTORY_SEPARATOR.$installedPath;
Chris@0 2210 }
Chris@0 2211
Chris@0 2212 $resolvedInstalledPaths[] = $installedPath;
Chris@0 2213 }
Chris@0 2214
Chris@0 2215 return $resolvedInstalledPaths;
Chris@0 2216
Chris@0 2217 }//end getInstalledStandardPaths()
Chris@0 2218
Chris@0 2219
Chris@0 2220 /**
Chris@0 2221 * Get a list of all coding standards installed.
Chris@0 2222 *
Chris@0 2223 * Coding standards are directories located in the
Chris@0 2224 * CodeSniffer/Standards directory. Valid coding standards
Chris@0 2225 * include a Sniffs subdirectory.
Chris@0 2226 *
Chris@0 2227 * @param boolean $includeGeneric If true, the special "Generic"
Chris@0 2228 * coding standard will be included
Chris@0 2229 * if installed.
Chris@0 2230 * @param string $standardsDir A specific directory to look for standards
Chris@0 2231 * in. If not specified, PHP_CodeSniffer will
Chris@0 2232 * look in its default locations.
Chris@0 2233 *
Chris@0 2234 * @return array
Chris@0 2235 * @see isInstalledStandard()
Chris@0 2236 */
Chris@0 2237 public static function getInstalledStandards(
Chris@0 2238 $includeGeneric=false,
Chris@0 2239 $standardsDir=''
Chris@0 2240 ) {
Chris@0 2241 $installedStandards = array();
Chris@0 2242
Chris@0 2243 if ($standardsDir === '') {
Chris@0 2244 $installedPaths = self::getInstalledStandardPaths();
Chris@0 2245 } else {
Chris@0 2246 $installedPaths = array($standardsDir);
Chris@0 2247 }
Chris@0 2248
Chris@0 2249 foreach ($installedPaths as $standardsDir) {
Chris@0 2250 $di = new DirectoryIterator($standardsDir);
Chris@0 2251 foreach ($di as $file) {
Chris@0 2252 if ($file->isDir() === true && $file->isDot() === false) {
Chris@0 2253 $filename = $file->getFilename();
Chris@0 2254
Chris@0 2255 // Ignore the special "Generic" standard.
Chris@0 2256 if ($includeGeneric === false && $filename === 'Generic') {
Chris@0 2257 continue;
Chris@0 2258 }
Chris@0 2259
Chris@0 2260 // Valid coding standard dirs include a ruleset.
Chris@0 2261 $csFile = $file->getPathname().'/ruleset.xml';
Chris@0 2262 if (is_file($csFile) === true) {
Chris@0 2263 $installedStandards[] = $filename;
Chris@0 2264 }
Chris@0 2265 }
Chris@0 2266 }
Chris@0 2267 }//end foreach
Chris@0 2268
Chris@0 2269 return $installedStandards;
Chris@0 2270
Chris@0 2271 }//end getInstalledStandards()
Chris@0 2272
Chris@0 2273
Chris@0 2274 /**
Chris@0 2275 * Determine if a standard is installed.
Chris@0 2276 *
Chris@0 2277 * Coding standards are directories located in the
Chris@0 2278 * CodeSniffer/Standards directory. Valid coding standards
Chris@0 2279 * include a ruleset.xml file.
Chris@0 2280 *
Chris@0 2281 * @param string $standard The name of the coding standard.
Chris@0 2282 *
Chris@0 2283 * @return boolean
Chris@0 2284 * @see getInstalledStandards()
Chris@0 2285 */
Chris@0 2286 public static function isInstalledStandard($standard)
Chris@0 2287 {
Chris@0 2288 $path = self::getInstalledStandardPath($standard);
Chris@0 2289 if ($path !== null && strpos($path, 'ruleset.xml') !== false) {
Chris@0 2290 return true;
Chris@0 2291 } else {
Chris@0 2292 // This could be a custom standard, installed outside our
Chris@0 2293 // standards directory.
Chris@0 2294 $standard = self::realPath($standard);
Chris@0 2295
Chris@0 2296 // Might be an actual ruleset file itself.
Chris@0 2297 // If it has an XML extension, let's at least try it.
Chris@0 2298 if (is_file($standard) === true
Chris@0 2299 && (substr(strtolower($standard), -4) === '.xml'
Chris@0 2300 || substr(strtolower($standard), -9) === '.xml.dist')
Chris@0 2301 ) {
Chris@0 2302 return true;
Chris@0 2303 }
Chris@0 2304
Chris@0 2305 // If it is a directory with a ruleset.xml file in it,
Chris@0 2306 // it is a standard.
Chris@0 2307 $ruleset = rtrim($standard, ' /\\').DIRECTORY_SEPARATOR.'ruleset.xml';
Chris@0 2308 if (is_file($ruleset) === true) {
Chris@0 2309 return true;
Chris@0 2310 }
Chris@0 2311 }//end if
Chris@0 2312
Chris@0 2313 return false;
Chris@0 2314
Chris@0 2315 }//end isInstalledStandard()
Chris@0 2316
Chris@0 2317
Chris@0 2318 /**
Chris@0 2319 * Return the path of an installed coding standard.
Chris@0 2320 *
Chris@0 2321 * Coding standards are directories located in the
Chris@0 2322 * CodeSniffer/Standards directory. Valid coding standards
Chris@0 2323 * include a ruleset.xml file.
Chris@0 2324 *
Chris@0 2325 * @param string $standard The name of the coding standard.
Chris@0 2326 *
Chris@0 2327 * @return string|null
Chris@0 2328 */
Chris@0 2329 public static function getInstalledStandardPath($standard)
Chris@0 2330 {
Chris@0 2331 $installedPaths = self::getInstalledStandardPaths();
Chris@0 2332 foreach ($installedPaths as $installedPath) {
Chris@0 2333 $standardPath = $installedPath.DIRECTORY_SEPARATOR.$standard;
Chris@0 2334 $path = self::realpath($standardPath.DIRECTORY_SEPARATOR.'ruleset.xml');
Chris@0 2335 if (is_file($path) === true) {
Chris@0 2336 return $path;
Chris@0 2337 } else if (self::isPharFile($standardPath) === true) {
Chris@0 2338 $path = self::realpath($standardPath);
Chris@0 2339 if ($path !== false) {
Chris@0 2340 return $path;
Chris@0 2341 }
Chris@0 2342 }
Chris@0 2343 }
Chris@0 2344
Chris@0 2345 return null;
Chris@0 2346
Chris@0 2347 }//end getInstalledStandardPath()
Chris@0 2348
Chris@0 2349
Chris@0 2350 /**
Chris@0 2351 * Get a single config value.
Chris@0 2352 *
Chris@0 2353 * Config data is stored in the data dir, in a file called
Chris@0 2354 * CodeSniffer.conf. It is a simple PHP array.
Chris@0 2355 *
Chris@0 2356 * @param string $key The name of the config value.
Chris@0 2357 *
Chris@0 2358 * @return string|null
Chris@0 2359 * @see setConfigData()
Chris@0 2360 * @see getAllConfigData()
Chris@0 2361 */
Chris@0 2362 public static function getConfigData($key)
Chris@0 2363 {
Chris@0 2364 $phpCodeSnifferConfig = self::getAllConfigData();
Chris@0 2365
Chris@0 2366 if ($phpCodeSnifferConfig === null) {
Chris@0 2367 return null;
Chris@0 2368 }
Chris@0 2369
Chris@0 2370 if (isset($phpCodeSnifferConfig[$key]) === false) {
Chris@0 2371 return null;
Chris@0 2372 }
Chris@0 2373
Chris@0 2374 return $phpCodeSnifferConfig[$key];
Chris@0 2375
Chris@0 2376 }//end getConfigData()
Chris@0 2377
Chris@0 2378
Chris@0 2379 /**
Chris@0 2380 * Set a single config value.
Chris@0 2381 *
Chris@0 2382 * Config data is stored in the data dir, in a file called
Chris@0 2383 * CodeSniffer.conf. It is a simple PHP array.
Chris@0 2384 *
Chris@0 2385 * @param string $key The name of the config value.
Chris@0 2386 * @param string|null $value The value to set. If null, the config
Chris@0 2387 * entry is deleted, reverting it to the
Chris@0 2388 * default value.
Chris@0 2389 * @param boolean $temp Set this config data temporarily for this
Chris@0 2390 * script run. This will not write the config
Chris@0 2391 * data to the config file.
Chris@0 2392 *
Chris@0 2393 * @return boolean
Chris@0 2394 * @see getConfigData()
Chris@0 2395 * @throws PHP_CodeSniffer_Exception If the config file can not be written.
Chris@0 2396 */
Chris@0 2397 public static function setConfigData($key, $value, $temp=false)
Chris@0 2398 {
Chris@0 2399 if ($temp === false) {
Chris@0 2400 $path = '';
Chris@0 2401 if (is_callable('Phar::running') === true) {
Chris@0 2402 $path = Phar::running(false);
Chris@0 2403 }
Chris@0 2404
Chris@0 2405 if ($path !== '') {
Chris@0 2406 $configFile = dirname($path).'/CodeSniffer.conf';
Chris@0 2407 } else {
Chris@0 2408 $configFile = dirname(__FILE__).'/CodeSniffer.conf';
Chris@0 2409 if (is_file($configFile) === false
Chris@0 2410 && strpos('@data_dir@', '@data_dir') === false
Chris@0 2411 ) {
Chris@0 2412 // If data_dir was replaced, this is a PEAR install and we can
Chris@0 2413 // use the PEAR data dir to store the conf file.
Chris@0 2414 $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
Chris@0 2415 }
Chris@0 2416 }
Chris@0 2417
Chris@0 2418 if (is_file($configFile) === true
Chris@0 2419 && is_writable($configFile) === false
Chris@0 2420 ) {
Chris@0 2421 $error = 'Config file '.$configFile.' is not writable';
Chris@0 2422 throw new PHP_CodeSniffer_Exception($error);
Chris@0 2423 }
Chris@0 2424 }//end if
Chris@0 2425
Chris@0 2426 $phpCodeSnifferConfig = self::getAllConfigData();
Chris@0 2427
Chris@0 2428 if ($value === null) {
Chris@0 2429 if (isset($phpCodeSnifferConfig[$key]) === true) {
Chris@0 2430 unset($phpCodeSnifferConfig[$key]);
Chris@0 2431 }
Chris@0 2432 } else {
Chris@0 2433 $phpCodeSnifferConfig[$key] = $value;
Chris@0 2434 }
Chris@0 2435
Chris@0 2436 if ($temp === false) {
Chris@0 2437 $output = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
Chris@0 2438 $output .= var_export($phpCodeSnifferConfig, true);
Chris@0 2439 $output .= "\n?".'>';
Chris@0 2440
Chris@0 2441 if (file_put_contents($configFile, $output) === false) {
Chris@0 2442 return false;
Chris@0 2443 }
Chris@0 2444 }
Chris@0 2445
Chris@0 2446 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
Chris@0 2447
Chris@0 2448 return true;
Chris@0 2449
Chris@0 2450 }//end setConfigData()
Chris@0 2451
Chris@0 2452
Chris@0 2453 /**
Chris@0 2454 * Get all config data in an array.
Chris@0 2455 *
Chris@0 2456 * @return array<string, string>
Chris@0 2457 * @see getConfigData()
Chris@0 2458 */
Chris@0 2459 public static function getAllConfigData()
Chris@0 2460 {
Chris@0 2461 if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']) === true) {
Chris@0 2462 return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
Chris@0 2463 }
Chris@0 2464
Chris@0 2465 $path = '';
Chris@0 2466 if (is_callable('Phar::running') === true) {
Chris@0 2467 $path = Phar::running(false);
Chris@0 2468 }
Chris@0 2469
Chris@0 2470 if ($path !== '') {
Chris@0 2471 $configFile = dirname($path).'/CodeSniffer.conf';
Chris@0 2472 } else {
Chris@0 2473 $configFile = dirname(__FILE__).'/CodeSniffer.conf';
Chris@0 2474 if (is_file($configFile) === false) {
Chris@0 2475 $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
Chris@0 2476 }
Chris@0 2477 }
Chris@0 2478
Chris@0 2479 if (is_file($configFile) === false) {
Chris@0 2480 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = array();
Chris@0 2481 return array();
Chris@0 2482 }
Chris@0 2483
Chris@0 2484 include $configFile;
Chris@0 2485 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
Chris@0 2486 return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
Chris@0 2487
Chris@0 2488 }//end getAllConfigData()
Chris@0 2489
Chris@0 2490
Chris@0 2491 /**
Chris@0 2492 * Return TRUE, if the path is a phar file.
Chris@0 2493 *
Chris@0 2494 * @param string $path The path to use.
Chris@0 2495 *
Chris@0 2496 * @return mixed
Chris@0 2497 */
Chris@0 2498 public static function isPharFile($path)
Chris@0 2499 {
Chris@0 2500 if (strpos($path, 'phar://') === 0) {
Chris@0 2501 return true;
Chris@0 2502 }
Chris@0 2503
Chris@0 2504 return false;
Chris@0 2505
Chris@0 2506 }//end isPharFile()
Chris@0 2507
Chris@0 2508
Chris@0 2509 /**
Chris@0 2510 * CodeSniffer alternative for realpath.
Chris@0 2511 *
Chris@0 2512 * Allows for phar support.
Chris@0 2513 *
Chris@0 2514 * @param string $path The path to use.
Chris@0 2515 *
Chris@0 2516 * @return mixed
Chris@0 2517 */
Chris@0 2518 public static function realpath($path)
Chris@0 2519 {
Chris@0 2520 // Support the path replacement of ~ with the user's home directory.
Chris@0 2521 if (substr($path, 0, 2) === '~/') {
Chris@0 2522 $homeDir = getenv('HOME');
Chris@0 2523 if ($homeDir !== false) {
Chris@0 2524 $path = $homeDir.substr($path, 1);
Chris@0 2525 }
Chris@0 2526 }
Chris@0 2527
Chris@0 2528 // No extra work needed if this is not a phar file.
Chris@0 2529 if (self::isPharFile($path) === false) {
Chris@0 2530 return realpath($path);
Chris@0 2531 }
Chris@0 2532
Chris@0 2533 // Before trying to break down the file path,
Chris@0 2534 // check if it exists first because it will mostly not
Chris@0 2535 // change after running the below code.
Chris@0 2536 if (file_exists($path) === true) {
Chris@0 2537 return $path;
Chris@0 2538 }
Chris@0 2539
Chris@0 2540 $phar = Phar::running(false);
Chris@0 2541 $extra = str_replace('phar://'.$phar, '', $path);
Chris@0 2542 $path = realpath($phar);
Chris@0 2543 if ($path === false) {
Chris@0 2544 return false;
Chris@0 2545 }
Chris@0 2546
Chris@0 2547 $path = 'phar://'.$path.$extra;
Chris@0 2548 if (file_exists($path) === true) {
Chris@0 2549 return $path;
Chris@0 2550 }
Chris@0 2551
Chris@0 2552 return false;
Chris@0 2553
Chris@0 2554 }//end realpath()
Chris@0 2555
Chris@0 2556
Chris@0 2557 /**
Chris@0 2558 * CodeSniffer alternative for chdir().
Chris@0 2559 *
Chris@0 2560 * Allows for phar support.
Chris@0 2561 *
Chris@0 2562 * @param string $path The path to use.
Chris@0 2563 *
Chris@0 2564 * @return void
Chris@0 2565 */
Chris@0 2566 public static function chdir($path)
Chris@0 2567 {
Chris@0 2568 if (self::isPharFile($path) === true) {
Chris@0 2569 $phar = Phar::running(false);
Chris@0 2570 chdir(dirname($phar));
Chris@0 2571 } else {
Chris@0 2572 chdir($path);
Chris@0 2573 }
Chris@0 2574
Chris@0 2575 }//end chdir()
Chris@0 2576
Chris@0 2577
Chris@0 2578 }//end class