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

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents
children af1871eacc83
rev   line source
Chris@17 1 <?php
Chris@17 2 /**
Chris@17 3 * Stores the rules used to check and fix files.
Chris@17 4 *
Chris@17 5 * A ruleset object directly maps to a ruleset XML file.
Chris@17 6 *
Chris@17 7 * @author Greg Sherwood <gsherwood@squiz.net>
Chris@17 8 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
Chris@17 9 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
Chris@17 10 */
Chris@17 11
Chris@17 12 namespace PHP_CodeSniffer;
Chris@17 13
Chris@17 14 use PHP_CodeSniffer\Util;
Chris@17 15 use PHP_CodeSniffer\Exceptions\RuntimeException;
Chris@17 16
Chris@17 17 class Ruleset
Chris@17 18 {
Chris@17 19
Chris@17 20 /**
Chris@17 21 * The name of the coding standard being used.
Chris@17 22 *
Chris@17 23 * If a top-level standard includes other standards, or sniffs
Chris@17 24 * from other standards, only the name of the top-level standard
Chris@17 25 * will be stored in here.
Chris@17 26 *
Chris@17 27 * If multiple top-level standards are being loaded into
Chris@17 28 * a single ruleset object, this will store a comma separated list
Chris@17 29 * of the top-level standard names.
Chris@17 30 *
Chris@17 31 * @var string
Chris@17 32 */
Chris@17 33 public $name = '';
Chris@17 34
Chris@17 35 /**
Chris@17 36 * A list of file paths for the ruleset files being used.
Chris@17 37 *
Chris@17 38 * @var string[]
Chris@17 39 */
Chris@17 40 public $paths = [];
Chris@17 41
Chris@17 42 /**
Chris@17 43 * A list of regular expressions used to ignore specific sniffs for files and folders.
Chris@17 44 *
Chris@17 45 * Is also used to set global exclude patterns.
Chris@17 46 * The key is the regular expression and the value is the type
Chris@17 47 * of ignore pattern (absolute or relative).
Chris@17 48 *
Chris@17 49 * @var array<string, string>
Chris@17 50 */
Chris@17 51 public $ignorePatterns = [];
Chris@17 52
Chris@17 53 /**
Chris@17 54 * A list of regular expressions used to include specific sniffs for files and folders.
Chris@17 55 *
Chris@17 56 * The key is the sniff code and the value is an array with
Chris@17 57 * the key being a regular expression and the value is the type
Chris@17 58 * of ignore pattern (absolute or relative).
Chris@17 59 *
Chris@17 60 * @var array<string, array<string, string>>
Chris@17 61 */
Chris@17 62 public $includePatterns = [];
Chris@17 63
Chris@17 64 /**
Chris@17 65 * An array of sniff objects that are being used to check files.
Chris@17 66 *
Chris@17 67 * The key is the fully qualified name of the sniff class
Chris@17 68 * and the value is the sniff object.
Chris@17 69 *
Chris@17 70 * @var array<string, \PHP_CodeSniffer\Sniff>
Chris@17 71 */
Chris@17 72 public $sniffs = [];
Chris@17 73
Chris@17 74 /**
Chris@17 75 * A mapping of sniff codes to fully qualified class names.
Chris@17 76 *
Chris@17 77 * The key is the sniff code and the value
Chris@17 78 * is the fully qualified name of the sniff class.
Chris@17 79 *
Chris@17 80 * @var array<string, string>
Chris@17 81 */
Chris@17 82 public $sniffCodes = [];
Chris@17 83
Chris@17 84 /**
Chris@17 85 * An array of token types and the sniffs that are listening for them.
Chris@17 86 *
Chris@17 87 * The key is the token name being listened for and the value
Chris@17 88 * is the sniff object.
Chris@17 89 *
Chris@17 90 * @var array<int, \PHP_CodeSniffer\Sniff>
Chris@17 91 */
Chris@17 92 public $tokenListeners = [];
Chris@17 93
Chris@17 94 /**
Chris@17 95 * An array of rules from the ruleset.xml file.
Chris@17 96 *
Chris@17 97 * It may be empty, indicating that the ruleset does not override
Chris@17 98 * any of the default sniff settings.
Chris@17 99 *
Chris@17 100 * @var array<string, mixed>
Chris@17 101 */
Chris@17 102 public $ruleset = [];
Chris@17 103
Chris@17 104 /**
Chris@17 105 * The directories that the processed rulesets are in.
Chris@17 106 *
Chris@17 107 * @var string[]
Chris@17 108 */
Chris@17 109 protected $rulesetDirs = [];
Chris@17 110
Chris@17 111 /**
Chris@17 112 * The config data for the run.
Chris@17 113 *
Chris@17 114 * @var \PHP_CodeSniffer\Config
Chris@17 115 */
Chris@17 116 private $config = null;
Chris@17 117
Chris@17 118
Chris@17 119 /**
Chris@17 120 * Initialise the ruleset that the run will use.
Chris@17 121 *
Chris@17 122 * @param \PHP_CodeSniffer\Config $config The config data for the run.
Chris@17 123 *
Chris@17 124 * @return void
Chris@17 125 */
Chris@17 126 public function __construct(Config $config)
Chris@17 127 {
Chris@17 128 // Ignore sniff restrictions if caching is on.
Chris@17 129 $restrictions = [];
Chris@17 130 $exclusions = [];
Chris@17 131 if ($config->cache === false) {
Chris@17 132 $restrictions = $config->sniffs;
Chris@17 133 $exclusions = $config->exclude;
Chris@17 134 }
Chris@17 135
Chris@17 136 $this->config = $config;
Chris@17 137 $sniffs = [];
Chris@17 138
Chris@17 139 $standardPaths = [];
Chris@17 140 foreach ($config->standards as $standard) {
Chris@17 141 $installed = Util\Standards::getInstalledStandardPath($standard);
Chris@17 142 if ($installed === null) {
Chris@17 143 $standard = Util\Common::realpath($standard);
Chris@17 144 if (is_dir($standard) === true
Chris@17 145 && is_file(Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true
Chris@17 146 ) {
Chris@17 147 $standard = Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml');
Chris@17 148 }
Chris@17 149 } else {
Chris@17 150 $standard = $installed;
Chris@17 151 }
Chris@17 152
Chris@17 153 $standardPaths[] = $standard;
Chris@17 154 }
Chris@17 155
Chris@17 156 foreach ($standardPaths as $standard) {
Chris@17 157 $ruleset = @simplexml_load_string(file_get_contents($standard));
Chris@17 158 if ($ruleset !== false) {
Chris@17 159 $standardName = (string) $ruleset['name'];
Chris@17 160 if ($this->name !== '') {
Chris@17 161 $this->name .= ', ';
Chris@17 162 }
Chris@17 163
Chris@17 164 $this->name .= $standardName;
Chris@17 165 $this->paths[] = $standard;
Chris@17 166
Chris@17 167 // Allow autoloading of custom files inside this standard.
Chris@17 168 if (isset($ruleset['namespace']) === true) {
Chris@17 169 $namespace = (string) $ruleset['namespace'];
Chris@17 170 } else {
Chris@17 171 $namespace = basename(dirname($standard));
Chris@17 172 }
Chris@17 173
Chris@17 174 Autoload::addSearchPath(dirname($standard), $namespace);
Chris@17 175 }
Chris@17 176
Chris@17 177 if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) {
Chris@17 178 // In unit tests, only register the sniffs that the test wants and not the entire standard.
Chris@17 179 try {
Chris@17 180 foreach ($restrictions as $restriction) {
Chris@17 181 $sniffs = array_merge($sniffs, $this->expandRulesetReference($restriction, dirname($standard)));
Chris@17 182 }
Chris@17 183 } catch (RuntimeException $e) {
Chris@17 184 // Sniff reference could not be expanded, which probably means this
Chris@17 185 // is an installed standard. Let the unit test system take care of
Chris@17 186 // setting the correct sniff for testing.
Chris@17 187 return;
Chris@17 188 }
Chris@17 189
Chris@17 190 break;
Chris@17 191 }
Chris@17 192
Chris@17 193 if (PHP_CODESNIFFER_VERBOSITY === 1) {
Chris@17 194 echo "Registering sniffs in the $standardName standard... ";
Chris@17 195 if (count($config->standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@17 196 echo PHP_EOL;
Chris@17 197 }
Chris@17 198 }
Chris@17 199
Chris@17 200 $sniffs = array_merge($sniffs, $this->processRuleset($standard));
Chris@17 201 }//end foreach
Chris@17 202
Chris@17 203 $sniffRestrictions = [];
Chris@17 204 foreach ($restrictions as $sniffCode) {
Chris@17 205 $parts = explode('.', strtolower($sniffCode));
Chris@17 206 $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
Chris@17 207 $sniffRestrictions[$sniffName] = true;
Chris@17 208 }
Chris@17 209
Chris@17 210 $sniffExclusions = [];
Chris@17 211 foreach ($exclusions as $sniffCode) {
Chris@17 212 $parts = explode('.', strtolower($sniffCode));
Chris@17 213 $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
Chris@17 214 $sniffExclusions[$sniffName] = true;
Chris@17 215 }
Chris@17 216
Chris@17 217 $this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions);
Chris@17 218 $this->populateTokenListeners();
Chris@17 219
Chris@17 220 $numSniffs = count($this->sniffs);
Chris@17 221 if (PHP_CODESNIFFER_VERBOSITY === 1) {
Chris@17 222 echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
Chris@17 223 }
Chris@17 224
Chris@17 225 if ($numSniffs === 0) {
Chris@17 226 throw new RuntimeException('No sniffs were registered');
Chris@17 227 }
Chris@17 228
Chris@17 229 }//end __construct()
Chris@17 230
Chris@17 231
Chris@17 232 /**
Chris@17 233 * Prints a report showing the sniffs contained in a standard.
Chris@17 234 *
Chris@17 235 * @return void
Chris@17 236 */
Chris@17 237 public function explain()
Chris@17 238 {
Chris@17 239 $sniffs = array_keys($this->sniffCodes);
Chris@17 240 sort($sniffs);
Chris@17 241
Chris@17 242 ob_start();
Chris@17 243
Chris@17 244 $lastStandard = null;
Chris@17 245 $lastCount = '';
Chris@17 246 $sniffCount = count($sniffs);
Chris@17 247
Chris@17 248 // Add a dummy entry to the end so we loop
Chris@17 249 // one last time and clear the output buffer.
Chris@17 250 $sniffs[] = '';
Chris@17 251
Chris@17 252 echo PHP_EOL."The $this->name standard contains $sniffCount sniffs".PHP_EOL;
Chris@17 253
Chris@17 254 ob_start();
Chris@17 255
Chris@17 256 foreach ($sniffs as $i => $sniff) {
Chris@17 257 if ($i === $sniffCount) {
Chris@17 258 $currentStandard = null;
Chris@17 259 } else {
Chris@17 260 $currentStandard = substr($sniff, 0, strpos($sniff, '.'));
Chris@17 261 if ($lastStandard === null) {
Chris@17 262 $lastStandard = $currentStandard;
Chris@17 263 }
Chris@17 264 }
Chris@17 265
Chris@17 266 if ($currentStandard !== $lastStandard) {
Chris@17 267 $sniffList = ob_get_contents();
Chris@17 268 ob_end_clean();
Chris@17 269
Chris@17 270 echo PHP_EOL.$lastStandard.' ('.$lastCount.' sniff';
Chris@17 271 if ($lastCount > 1) {
Chris@17 272 echo 's';
Chris@17 273 }
Chris@17 274
Chris@17 275 echo ')'.PHP_EOL;
Chris@17 276 echo str_repeat('-', (strlen($lastStandard.$lastCount) + 10));
Chris@17 277 echo PHP_EOL;
Chris@17 278 echo $sniffList;
Chris@17 279
Chris@17 280 $lastStandard = $currentStandard;
Chris@17 281 $lastCount = 0;
Chris@17 282
Chris@17 283 if ($currentStandard === null) {
Chris@17 284 break;
Chris@17 285 }
Chris@17 286
Chris@17 287 ob_start();
Chris@17 288 }//end if
Chris@17 289
Chris@17 290 echo ' '.$sniff.PHP_EOL;
Chris@17 291 $lastCount++;
Chris@17 292 }//end foreach
Chris@17 293
Chris@17 294 }//end explain()
Chris@17 295
Chris@17 296
Chris@17 297 /**
Chris@17 298 * Processes a single ruleset and returns a list of the sniffs it represents.
Chris@17 299 *
Chris@17 300 * Rules founds within the ruleset are processed immediately, but sniff classes
Chris@17 301 * are not registered by this method.
Chris@17 302 *
Chris@17 303 * @param string $rulesetPath The path to a ruleset XML file.
Chris@17 304 * @param int $depth How many nested processing steps we are in. This
Chris@17 305 * is only used for debug output.
Chris@17 306 *
Chris@17 307 * @return string[]
Chris@17 308 * @throws RuntimeException If the ruleset path is invalid.
Chris@17 309 */
Chris@17 310 public function processRuleset($rulesetPath, $depth=0)
Chris@17 311 {
Chris@17 312 $rulesetPath = Util\Common::realpath($rulesetPath);
Chris@17 313 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 314 echo str_repeat("\t", $depth);
Chris@17 315 echo 'Processing ruleset '.Util\Common::stripBasepath($rulesetPath, $this->config->basepath).PHP_EOL;
Chris@17 316 }
Chris@17 317
Chris@17 318 $ruleset = @simplexml_load_string(file_get_contents($rulesetPath));
Chris@17 319 if ($ruleset === false) {
Chris@17 320 throw new RuntimeException("Ruleset $rulesetPath is not valid");
Chris@17 321 }
Chris@17 322
Chris@17 323 $ownSniffs = [];
Chris@17 324 $includedSniffs = [];
Chris@17 325 $excludedSniffs = [];
Chris@17 326
Chris@17 327 $rulesetDir = dirname($rulesetPath);
Chris@17 328 $this->rulesetDirs[] = $rulesetDir;
Chris@17 329
Chris@17 330 $sniffDir = $rulesetDir.DIRECTORY_SEPARATOR.'Sniffs';
Chris@17 331 if (is_dir($sniffDir) === true) {
Chris@17 332 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 333 echo str_repeat("\t", $depth);
Chris@17 334 echo "\tAdding sniff files from ".Util\Common::stripBasepath($sniffDir, $this->config->basepath).' directory'.PHP_EOL;
Chris@17 335 }
Chris@17 336
Chris@17 337 $ownSniffs = $this->expandSniffDirectory($sniffDir, $depth);
Chris@17 338 }
Chris@17 339
Chris@17 340 // Included custom autoloaders.
Chris@17 341 foreach ($ruleset->{'autoload'} as $autoload) {
Chris@17 342 if ($this->shouldProcessElement($autoload) === false) {
Chris@17 343 continue;
Chris@17 344 }
Chris@17 345
Chris@17 346 $autoloadPath = (string) $autoload;
Chris@17 347 if (is_file($autoloadPath) === false) {
Chris@17 348 $autoloadPath = Util\Common::realPath(dirname($rulesetPath).DIRECTORY_SEPARATOR.$autoloadPath);
Chris@17 349 }
Chris@17 350
Chris@17 351 if ($autoloadPath === false) {
Chris@17 352 throw new RuntimeException('The specified autoload file "'.$autoload.'" does not exist');
Chris@17 353 }
Chris@17 354
Chris@17 355 include_once $autoloadPath;
Chris@17 356
Chris@17 357 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 358 echo str_repeat("\t", $depth);
Chris@17 359 echo "\t=> included autoloader $autoloadPath".PHP_EOL;
Chris@17 360 }
Chris@17 361 }//end foreach
Chris@17 362
Chris@17 363 // Process custom sniff config settings.
Chris@17 364 foreach ($ruleset->{'config'} as $config) {
Chris@17 365 if ($this->shouldProcessElement($config) === false) {
Chris@17 366 continue;
Chris@17 367 }
Chris@17 368
Chris@17 369 Config::setConfigData((string) $config['name'], (string) $config['value'], true);
Chris@17 370 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 371 echo str_repeat("\t", $depth);
Chris@17 372 echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL;
Chris@17 373 }
Chris@17 374 }
Chris@17 375
Chris@17 376 foreach ($ruleset->rule as $rule) {
Chris@17 377 if (isset($rule['ref']) === false
Chris@17 378 || $this->shouldProcessElement($rule) === false
Chris@17 379 ) {
Chris@17 380 continue;
Chris@17 381 }
Chris@17 382
Chris@17 383 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 384 echo str_repeat("\t", $depth);
Chris@17 385 echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL;
Chris@17 386 }
Chris@17 387
Chris@17 388 $expandedSniffs = $this->expandRulesetReference((string) $rule['ref'], $rulesetDir, $depth);
Chris@17 389 $newSniffs = array_diff($expandedSniffs, $includedSniffs);
Chris@17 390 $includedSniffs = array_merge($includedSniffs, $expandedSniffs);
Chris@17 391
Chris@17 392 $parts = explode('.', $rule['ref']);
Chris@17 393 if (count($parts) === 4
Chris@17 394 && $parts[0] !== ''
Chris@17 395 && $parts[1] !== ''
Chris@17 396 && $parts[2] !== ''
Chris@17 397 ) {
Chris@17 398 $sniffCode = $parts[0].'.'.$parts[1].'.'.$parts[2];
Chris@17 399 if (isset($this->ruleset[$sniffCode]['severity']) === true
Chris@17 400 && $this->ruleset[$sniffCode]['severity'] === 0
Chris@17 401 ) {
Chris@17 402 // This sniff code has already been turned off, but now
Chris@17 403 // it is being explicitly included again, so turn it back on.
Chris@17 404 $this->ruleset[(string) $rule['ref']]['severity'] = 5;
Chris@17 405 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 406 echo str_repeat("\t", $depth);
Chris@17 407 echo "\t\t* disabling sniff exclusion for specific message code *".PHP_EOL;
Chris@17 408 echo str_repeat("\t", $depth);
Chris@17 409 echo "\t\t=> severity set to 5".PHP_EOL;
Chris@17 410 }
Chris@17 411 } else if (empty($newSniffs) === false) {
Chris@17 412 $newSniff = $newSniffs[0];
Chris@17 413 if (in_array($newSniff, $ownSniffs) === false) {
Chris@17 414 // Including a sniff that hasn't been included higher up, but
Chris@17 415 // only including a single message from it. So turn off all messages in
Chris@17 416 // the sniff, except this one.
Chris@17 417 $this->ruleset[$sniffCode]['severity'] = 0;
Chris@17 418 $this->ruleset[(string) $rule['ref']]['severity'] = 5;
Chris@17 419 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 420 echo str_repeat("\t", $depth);
Chris@17 421 echo "\t\tExcluding sniff \"".$sniffCode.'" except for "'.$parts[3].'"'.PHP_EOL;
Chris@17 422 }
Chris@17 423 }
Chris@17 424 }//end if
Chris@17 425 }//end if
Chris@17 426
Chris@17 427 if (isset($rule->exclude) === true) {
Chris@17 428 foreach ($rule->exclude as $exclude) {
Chris@17 429 if (isset($exclude['name']) === false) {
Chris@17 430 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 431 echo str_repeat("\t", $depth);
Chris@17 432 echo "\t\t* ignoring empty exclude rule *".PHP_EOL;
Chris@17 433 echo "\t\t\t=> ".$exclude->asXML().PHP_EOL;
Chris@17 434 }
Chris@17 435
Chris@17 436 continue;
Chris@17 437 }
Chris@17 438
Chris@17 439 if ($this->shouldProcessElement($exclude) === false) {
Chris@17 440 continue;
Chris@17 441 }
Chris@17 442
Chris@17 443 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 444 echo str_repeat("\t", $depth);
Chris@17 445 echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL;
Chris@17 446 }
Chris@17 447
Chris@17 448 // Check if a single code is being excluded, which is a shortcut
Chris@17 449 // for setting the severity of the message to 0.
Chris@17 450 $parts = explode('.', $exclude['name']);
Chris@17 451 if (count($parts) === 4) {
Chris@17 452 $this->ruleset[(string) $exclude['name']]['severity'] = 0;
Chris@17 453 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 454 echo str_repeat("\t", $depth);
Chris@17 455 echo "\t\t=> severity set to 0".PHP_EOL;
Chris@17 456 }
Chris@17 457 } else {
Chris@17 458 $excludedSniffs = array_merge(
Chris@17 459 $excludedSniffs,
Chris@17 460 $this->expandRulesetReference($exclude['name'], $rulesetDir, ($depth + 1))
Chris@17 461 );
Chris@17 462 }
Chris@17 463 }//end foreach
Chris@17 464 }//end if
Chris@17 465
Chris@17 466 $this->processRule($rule, $newSniffs, $depth);
Chris@17 467 }//end foreach
Chris@17 468
Chris@17 469 // Process custom command line arguments.
Chris@17 470 $cliArgs = [];
Chris@17 471 foreach ($ruleset->{'arg'} as $arg) {
Chris@17 472 if ($this->shouldProcessElement($arg) === false) {
Chris@17 473 continue;
Chris@17 474 }
Chris@17 475
Chris@17 476 if (isset($arg['name']) === true) {
Chris@17 477 $argString = '--'.(string) $arg['name'];
Chris@17 478 if (isset($arg['value']) === true) {
Chris@17 479 $argString .= '='.(string) $arg['value'];
Chris@17 480 }
Chris@17 481 } else {
Chris@17 482 $argString = '-'.(string) $arg['value'];
Chris@17 483 }
Chris@17 484
Chris@17 485 $cliArgs[] = $argString;
Chris@17 486
Chris@17 487 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 488 echo str_repeat("\t", $depth);
Chris@17 489 echo "\t=> set command line value $argString".PHP_EOL;
Chris@17 490 }
Chris@17 491 }//end foreach
Chris@17 492
Chris@17 493 // Set custom php ini values as CLI args.
Chris@17 494 foreach ($ruleset->{'ini'} as $arg) {
Chris@17 495 if ($this->shouldProcessElement($arg) === false) {
Chris@17 496 continue;
Chris@17 497 }
Chris@17 498
Chris@17 499 if (isset($arg['name']) === false) {
Chris@17 500 continue;
Chris@17 501 }
Chris@17 502
Chris@17 503 $name = (string) $arg['name'];
Chris@17 504 $argString = $name;
Chris@17 505 if (isset($arg['value']) === true) {
Chris@17 506 $value = (string) $arg['value'];
Chris@17 507 $argString .= "=$value";
Chris@17 508 } else {
Chris@17 509 $value = 'true';
Chris@17 510 }
Chris@17 511
Chris@17 512 $cliArgs[] = '-d';
Chris@17 513 $cliArgs[] = $argString;
Chris@17 514
Chris@17 515 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 516 echo str_repeat("\t", $depth);
Chris@17 517 echo "\t=> set PHP ini value $name to $value".PHP_EOL;
Chris@17 518 }
Chris@17 519 }//end foreach
Chris@17 520
Chris@17 521 if (empty($this->config->files) === true) {
Chris@17 522 // Process hard-coded file paths.
Chris@17 523 foreach ($ruleset->{'file'} as $file) {
Chris@17 524 $file = (string) $file;
Chris@17 525 $cliArgs[] = $file;
Chris@17 526 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 527 echo str_repeat("\t", $depth);
Chris@17 528 echo "\t=> added \"$file\" to the file list".PHP_EOL;
Chris@17 529 }
Chris@17 530 }
Chris@17 531 }
Chris@17 532
Chris@17 533 if (empty($cliArgs) === false) {
Chris@17 534 // Change the directory so all relative paths are worked
Chris@17 535 // out based on the location of the ruleset instead of
Chris@17 536 // the location of the user.
Chris@17 537 $inPhar = Util\Common::isPharFile($rulesetDir);
Chris@17 538 if ($inPhar === false) {
Chris@17 539 $currentDir = getcwd();
Chris@17 540 chdir($rulesetDir);
Chris@17 541 }
Chris@17 542
Chris@17 543 $this->config->setCommandLineValues($cliArgs);
Chris@17 544
Chris@17 545 if ($inPhar === false) {
Chris@17 546 chdir($currentDir);
Chris@17 547 }
Chris@17 548 }
Chris@17 549
Chris@17 550 // Process custom ignore pattern rules.
Chris@17 551 foreach ($ruleset->{'exclude-pattern'} as $pattern) {
Chris@17 552 if ($this->shouldProcessElement($pattern) === false) {
Chris@17 553 continue;
Chris@17 554 }
Chris@17 555
Chris@17 556 if (isset($pattern['type']) === false) {
Chris@17 557 $pattern['type'] = 'absolute';
Chris@17 558 }
Chris@17 559
Chris@17 560 $this->ignorePatterns[(string) $pattern] = (string) $pattern['type'];
Chris@17 561 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 562 echo str_repeat("\t", $depth);
Chris@17 563 echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
Chris@17 564 }
Chris@17 565 }
Chris@17 566
Chris@17 567 $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs));
Chris@17 568 $excludedSniffs = array_unique($excludedSniffs);
Chris@17 569
Chris@17 570 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 571 $included = count($includedSniffs);
Chris@17 572 $excluded = count($excludedSniffs);
Chris@17 573 echo str_repeat("\t", $depth);
Chris@17 574 echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL;
Chris@17 575 }
Chris@17 576
Chris@17 577 // Merge our own sniff list with our externally included
Chris@17 578 // sniff list, but filter out any excluded sniffs.
Chris@17 579 $files = [];
Chris@17 580 foreach ($includedSniffs as $sniff) {
Chris@17 581 if (in_array($sniff, $excludedSniffs) === true) {
Chris@17 582 continue;
Chris@17 583 } else {
Chris@17 584 $files[] = Util\Common::realpath($sniff);
Chris@17 585 }
Chris@17 586 }
Chris@17 587
Chris@17 588 return $files;
Chris@17 589
Chris@17 590 }//end processRuleset()
Chris@17 591
Chris@17 592
Chris@17 593 /**
Chris@17 594 * Expands a directory into a list of sniff files within.
Chris@17 595 *
Chris@17 596 * @param string $directory The path to a directory.
Chris@17 597 * @param int $depth How many nested processing steps we are in. This
Chris@17 598 * is only used for debug output.
Chris@17 599 *
Chris@17 600 * @return array
Chris@17 601 */
Chris@17 602 private function expandSniffDirectory($directory, $depth=0)
Chris@17 603 {
Chris@17 604 $sniffs = [];
Chris@17 605
Chris@17 606 $rdi = new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
Chris@17 607 $di = new \RecursiveIteratorIterator($rdi, 0, \RecursiveIteratorIterator::CATCH_GET_CHILD);
Chris@17 608
Chris@17 609 $dirLen = strlen($directory);
Chris@17 610
Chris@17 611 foreach ($di as $file) {
Chris@17 612 $filename = $file->getFilename();
Chris@17 613
Chris@17 614 // Skip hidden files.
Chris@17 615 if (substr($filename, 0, 1) === '.') {
Chris@17 616 continue;
Chris@17 617 }
Chris@17 618
Chris@17 619 // We are only interested in PHP and sniff files.
Chris@17 620 $fileParts = explode('.', $filename);
Chris@17 621 if (array_pop($fileParts) !== 'php') {
Chris@17 622 continue;
Chris@17 623 }
Chris@17 624
Chris@17 625 $basename = basename($filename, '.php');
Chris@17 626 if (substr($basename, -5) !== 'Sniff') {
Chris@17 627 continue;
Chris@17 628 }
Chris@17 629
Chris@17 630 $path = $file->getPathname();
Chris@17 631
Chris@17 632 // Skip files in hidden directories within the Sniffs directory of this
Chris@17 633 // standard. We use the offset with strpos() to allow hidden directories
Chris@17 634 // before, valid example:
Chris@17 635 // /home/foo/.composer/vendor/squiz/custom_tool/MyStandard/Sniffs/...
Chris@17 636 if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) {
Chris@17 637 continue;
Chris@17 638 }
Chris@17 639
Chris@17 640 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 641 echo str_repeat("\t", $depth);
Chris@17 642 echo "\t\t=> ".Util\Common::stripBasepath($path, $this->config->basepath).PHP_EOL;
Chris@17 643 }
Chris@17 644
Chris@17 645 $sniffs[] = $path;
Chris@17 646 }//end foreach
Chris@17 647
Chris@17 648 return $sniffs;
Chris@17 649
Chris@17 650 }//end expandSniffDirectory()
Chris@17 651
Chris@17 652
Chris@17 653 /**
Chris@17 654 * Expands a ruleset reference into a list of sniff files.
Chris@17 655 *
Chris@17 656 * @param string $ref The reference from the ruleset XML file.
Chris@17 657 * @param string $rulesetDir The directory of the ruleset XML file, used to
Chris@17 658 * evaluate relative paths.
Chris@17 659 * @param int $depth How many nested processing steps we are in. This
Chris@17 660 * is only used for debug output.
Chris@17 661 *
Chris@17 662 * @return array
Chris@17 663 * @throws RuntimeException If the reference is invalid.
Chris@17 664 */
Chris@17 665 private function expandRulesetReference($ref, $rulesetDir, $depth=0)
Chris@17 666 {
Chris@17 667 // Ignore internal sniffs codes as they are used to only
Chris@17 668 // hide and change internal messages.
Chris@17 669 if (substr($ref, 0, 9) === 'Internal.') {
Chris@17 670 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 671 echo str_repeat("\t", $depth);
Chris@17 672 echo "\t\t* ignoring internal sniff code *".PHP_EOL;
Chris@17 673 }
Chris@17 674
Chris@17 675 return [];
Chris@17 676 }
Chris@17 677
Chris@17 678 // As sniffs can't begin with a full stop, assume references in
Chris@17 679 // this format are relative paths and attempt to convert them
Chris@17 680 // to absolute paths. If this fails, let the reference run through
Chris@17 681 // the normal checks and have it fail as normal.
Chris@17 682 if (substr($ref, 0, 1) === '.') {
Chris@17 683 $realpath = Util\Common::realpath($rulesetDir.'/'.$ref);
Chris@17 684 if ($realpath !== false) {
Chris@17 685 $ref = $realpath;
Chris@17 686 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 687 echo str_repeat("\t", $depth);
Chris@17 688 echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
Chris@17 689 }
Chris@17 690 }
Chris@17 691 }
Chris@17 692
Chris@17 693 // As sniffs can't begin with a tilde, assume references in
Chris@17 694 // this format are relative to the user's home directory.
Chris@17 695 if (substr($ref, 0, 2) === '~/') {
Chris@17 696 $realpath = Util\Common::realpath($ref);
Chris@17 697 if ($realpath !== false) {
Chris@17 698 $ref = $realpath;
Chris@17 699 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 700 echo str_repeat("\t", $depth);
Chris@17 701 echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
Chris@17 702 }
Chris@17 703 }
Chris@17 704 }
Chris@17 705
Chris@17 706 if (is_file($ref) === true) {
Chris@17 707 if (substr($ref, -9) === 'Sniff.php') {
Chris@17 708 // A single external sniff.
Chris@17 709 $this->rulesetDirs[] = dirname(dirname(dirname($ref)));
Chris@17 710 return [$ref];
Chris@17 711 }
Chris@17 712 } else {
Chris@17 713 // See if this is a whole standard being referenced.
Chris@17 714 $path = Util\Standards::getInstalledStandardPath($ref);
Chris@17 715 if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) {
Chris@17 716 // If the ruleset exists inside the phar file, use it.
Chris@17 717 if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
Chris@17 718 $path = $path.DIRECTORY_SEPARATOR.'ruleset.xml';
Chris@17 719 } else {
Chris@17 720 $path = null;
Chris@17 721 }
Chris@17 722 }
Chris@17 723
Chris@17 724 if ($path !== null) {
Chris@17 725 $ref = $path;
Chris@17 726 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 727 echo str_repeat("\t", $depth);
Chris@17 728 echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
Chris@17 729 }
Chris@17 730 } else if (is_dir($ref) === false) {
Chris@17 731 // Work out the sniff path.
Chris@17 732 $sepPos = strpos($ref, DIRECTORY_SEPARATOR);
Chris@17 733 if ($sepPos !== false) {
Chris@17 734 $stdName = substr($ref, 0, $sepPos);
Chris@17 735 $path = substr($ref, $sepPos);
Chris@17 736 } else {
Chris@17 737 $parts = explode('.', $ref);
Chris@17 738 $stdName = $parts[0];
Chris@17 739 if (count($parts) === 1) {
Chris@17 740 // A whole standard?
Chris@17 741 $path = '';
Chris@17 742 } else if (count($parts) === 2) {
Chris@17 743 // A directory of sniffs?
Chris@17 744 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1];
Chris@17 745 } else {
Chris@17 746 // A single sniff?
Chris@17 747 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php';
Chris@17 748 }
Chris@17 749 }
Chris@17 750
Chris@17 751 $newRef = false;
Chris@17 752 $stdPath = Util\Standards::getInstalledStandardPath($stdName);
Chris@17 753 if ($stdPath !== null && $path !== '') {
Chris@17 754 if (Util\Common::isPharFile($stdPath) === true
Chris@17 755 && strpos($stdPath, 'ruleset.xml') === false
Chris@17 756 ) {
Chris@17 757 // Phar files can only return the directory,
Chris@17 758 // since ruleset can be omitted if building one standard.
Chris@17 759 $newRef = Util\Common::realpath($stdPath.$path);
Chris@17 760 } else {
Chris@17 761 $newRef = Util\Common::realpath(dirname($stdPath).$path);
Chris@17 762 }
Chris@17 763 }
Chris@17 764
Chris@17 765 if ($newRef === false) {
Chris@17 766 // The sniff is not locally installed, so check if it is being
Chris@17 767 // referenced as a remote sniff outside the install. We do this
Chris@17 768 // by looking through all directories where we have found ruleset
Chris@17 769 // files before, looking for ones for this particular standard,
Chris@17 770 // and seeing if it is in there.
Chris@17 771 foreach ($this->rulesetDirs as $dir) {
Chris@17 772 if (strtolower(basename($dir)) !== strtolower($stdName)) {
Chris@17 773 continue;
Chris@17 774 }
Chris@17 775
Chris@17 776 $newRef = Util\Common::realpath($dir.$path);
Chris@17 777
Chris@17 778 if ($newRef !== false) {
Chris@17 779 $ref = $newRef;
Chris@17 780 }
Chris@17 781 }
Chris@17 782 } else {
Chris@17 783 $ref = $newRef;
Chris@17 784 }
Chris@17 785
Chris@17 786 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 787 echo str_repeat("\t", $depth);
Chris@17 788 echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL;
Chris@17 789 }
Chris@17 790 }//end if
Chris@17 791 }//end if
Chris@17 792
Chris@17 793 if (is_dir($ref) === true) {
Chris@17 794 if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) {
Chris@17 795 // We are referencing an external coding standard.
Chris@17 796 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 797 echo str_repeat("\t", $depth);
Chris@17 798 echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL;
Chris@17 799 }
Chris@17 800
Chris@17 801 return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2));
Chris@17 802 } else {
Chris@17 803 // We are referencing a whole directory of sniffs.
Chris@17 804 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 805 echo str_repeat("\t", $depth);
Chris@17 806 echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL;
Chris@17 807 echo str_repeat("\t", $depth);
Chris@17 808 echo "\t\tAdding sniff files from directory".PHP_EOL;
Chris@17 809 }
Chris@17 810
Chris@17 811 return $this->expandSniffDirectory($ref, ($depth + 1));
Chris@17 812 }
Chris@17 813 } else {
Chris@17 814 if (is_file($ref) === false) {
Chris@17 815 $error = "Referenced sniff \"$ref\" does not exist";
Chris@17 816 throw new RuntimeException($error);
Chris@17 817 }
Chris@17 818
Chris@17 819 if (substr($ref, -9) === 'Sniff.php') {
Chris@17 820 // A single sniff.
Chris@17 821 return [$ref];
Chris@17 822 } else {
Chris@17 823 // Assume an external ruleset.xml file.
Chris@17 824 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 825 echo str_repeat("\t", $depth);
Chris@17 826 echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL;
Chris@17 827 }
Chris@17 828
Chris@17 829 return $this->processRuleset($ref, ($depth + 2));
Chris@17 830 }
Chris@17 831 }//end if
Chris@17 832
Chris@17 833 }//end expandRulesetReference()
Chris@17 834
Chris@17 835
Chris@17 836 /**
Chris@17 837 * Processes a rule from a ruleset XML file, overriding built-in defaults.
Chris@17 838 *
Chris@17 839 * @param SimpleXMLElement $rule The rule object from a ruleset XML file.
Chris@17 840 * @param string[] $newSniffs An array of sniffs that got included by this rule.
Chris@17 841 * @param int $depth How many nested processing steps we are in.
Chris@17 842 * This is only used for debug output.
Chris@17 843 *
Chris@17 844 * @return void
Chris@17 845 * @throws RuntimeException If rule settings are invalid.
Chris@17 846 */
Chris@17 847 private function processRule($rule, $newSniffs, $depth=0)
Chris@17 848 {
Chris@17 849 $ref = (string) $rule['ref'];
Chris@17 850 $todo = [$ref];
Chris@17 851
Chris@17 852 $parts = explode('.', $ref);
Chris@17 853 if (count($parts) <= 2) {
Chris@17 854 // We are processing a standard or a category of sniffs.
Chris@17 855 foreach ($newSniffs as $sniffFile) {
Chris@17 856 $parts = explode(DIRECTORY_SEPARATOR, $sniffFile);
Chris@17 857 $sniffName = array_pop($parts);
Chris@17 858 $sniffCategory = array_pop($parts);
Chris@17 859 array_pop($parts);
Chris@17 860 $sniffStandard = array_pop($parts);
Chris@17 861 $todo[] = $sniffStandard.'.'.$sniffCategory.'.'.substr($sniffName, 0, -9);
Chris@17 862 }
Chris@17 863 }
Chris@17 864
Chris@17 865 foreach ($todo as $code) {
Chris@17 866 // Custom severity.
Chris@17 867 if (isset($rule->severity) === true
Chris@17 868 && $this->shouldProcessElement($rule->severity) === true
Chris@17 869 ) {
Chris@17 870 if (isset($this->ruleset[$code]) === false) {
Chris@17 871 $this->ruleset[$code] = [];
Chris@17 872 }
Chris@17 873
Chris@17 874 $this->ruleset[$code]['severity'] = (int) $rule->severity;
Chris@17 875 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 876 echo str_repeat("\t", $depth);
Chris@17 877 echo "\t\t=> severity set to ".(int) $rule->severity;
Chris@17 878 if ($code !== $ref) {
Chris@17 879 echo " for $code";
Chris@17 880 }
Chris@17 881
Chris@17 882 echo PHP_EOL;
Chris@17 883 }
Chris@17 884 }
Chris@17 885
Chris@17 886 // Custom message type.
Chris@17 887 if (isset($rule->type) === true
Chris@17 888 && $this->shouldProcessElement($rule->type) === true
Chris@17 889 ) {
Chris@17 890 if (isset($this->ruleset[$code]) === false) {
Chris@17 891 $this->ruleset[$code] = [];
Chris@17 892 }
Chris@17 893
Chris@17 894 $type = strtolower((string) $rule->type);
Chris@17 895 if ($type !== 'error' && $type !== 'warning') {
Chris@17 896 throw new RuntimeException("Message type \"$type\" is invalid; must be \"error\" or \"warning\"");
Chris@17 897 }
Chris@17 898
Chris@17 899 $this->ruleset[$code]['type'] = $type;
Chris@17 900 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 901 echo str_repeat("\t", $depth);
Chris@17 902 echo "\t\t=> message type set to ".(string) $rule->type;
Chris@17 903 if ($code !== $ref) {
Chris@17 904 echo " for $code";
Chris@17 905 }
Chris@17 906
Chris@17 907 echo PHP_EOL;
Chris@17 908 }
Chris@17 909 }//end if
Chris@17 910
Chris@17 911 // Custom message.
Chris@17 912 if (isset($rule->message) === true
Chris@17 913 && $this->shouldProcessElement($rule->message) === true
Chris@17 914 ) {
Chris@17 915 if (isset($this->ruleset[$code]) === false) {
Chris@17 916 $this->ruleset[$code] = [];
Chris@17 917 }
Chris@17 918
Chris@17 919 $this->ruleset[$code]['message'] = (string) $rule->message;
Chris@17 920 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 921 echo str_repeat("\t", $depth);
Chris@17 922 echo "\t\t=> message set to ".(string) $rule->message;
Chris@17 923 if ($code !== $ref) {
Chris@17 924 echo " for $code";
Chris@17 925 }
Chris@17 926
Chris@17 927 echo PHP_EOL;
Chris@17 928 }
Chris@17 929 }
Chris@17 930
Chris@17 931 // Custom properties.
Chris@17 932 if (isset($rule->properties) === true
Chris@17 933 && $this->shouldProcessElement($rule->properties) === true
Chris@17 934 ) {
Chris@17 935 foreach ($rule->properties->property as $prop) {
Chris@17 936 if ($this->shouldProcessElement($prop) === false) {
Chris@17 937 continue;
Chris@17 938 }
Chris@17 939
Chris@17 940 if (isset($this->ruleset[$code]) === false) {
Chris@17 941 $this->ruleset[$code] = [
Chris@17 942 'properties' => [],
Chris@17 943 ];
Chris@17 944 } else if (isset($this->ruleset[$code]['properties']) === false) {
Chris@17 945 $this->ruleset[$code]['properties'] = [];
Chris@17 946 }
Chris@17 947
Chris@17 948 $name = (string) $prop['name'];
Chris@17 949 if (isset($prop['type']) === true
Chris@17 950 && (string) $prop['type'] === 'array'
Chris@17 951 ) {
Chris@17 952 $values = [];
Chris@17 953 if (isset($prop['extend']) === true
Chris@17 954 && (string) $prop['extend'] === 'true'
Chris@17 955 && isset($this->ruleset[$code]['properties'][$name]) === true
Chris@17 956 ) {
Chris@17 957 $values = $this->ruleset[$code]['properties'][$name];
Chris@17 958 }
Chris@17 959
Chris@17 960 if (isset($prop->element) === true) {
Chris@17 961 $printValue = '';
Chris@17 962 foreach ($prop->element as $element) {
Chris@17 963 if ($this->shouldProcessElement($element) === false) {
Chris@17 964 continue;
Chris@17 965 }
Chris@17 966
Chris@17 967 $value = (string) $element['value'];
Chris@17 968 if (isset($element['key']) === true) {
Chris@17 969 $key = (string) $element['key'];
Chris@17 970 $values[$key] = $value;
Chris@17 971 $printValue .= $key.'=>'.$value.',';
Chris@17 972 } else {
Chris@17 973 $values[] = $value;
Chris@17 974 $printValue .= $value.',';
Chris@17 975 }
Chris@17 976 }
Chris@17 977
Chris@17 978 $printValue = rtrim($printValue, ',');
Chris@17 979 } else {
Chris@17 980 $value = (string) $prop['value'];
Chris@17 981 $printValue = $value;
Chris@17 982 foreach (explode(',', $value) as $val) {
Chris@17 983 list($k, $v) = explode('=>', $val.'=>');
Chris@17 984 if ($v !== '') {
Chris@17 985 $values[trim($k)] = trim($v);
Chris@17 986 } else {
Chris@17 987 $values[] = trim($k);
Chris@17 988 }
Chris@17 989 }
Chris@17 990 }//end if
Chris@17 991
Chris@17 992 $this->ruleset[$code]['properties'][$name] = $values;
Chris@17 993 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 994 echo str_repeat("\t", $depth);
Chris@17 995 echo "\t\t=> array property \"$name\" set to \"$printValue\"";
Chris@17 996 if ($code !== $ref) {
Chris@17 997 echo " for $code";
Chris@17 998 }
Chris@17 999
Chris@17 1000 echo PHP_EOL;
Chris@17 1001 }
Chris@17 1002 } else {
Chris@17 1003 $this->ruleset[$code]['properties'][$name] = (string) $prop['value'];
Chris@17 1004 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 1005 echo str_repeat("\t", $depth);
Chris@17 1006 echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"';
Chris@17 1007 if ($code !== $ref) {
Chris@17 1008 echo " for $code";
Chris@17 1009 }
Chris@17 1010
Chris@17 1011 echo PHP_EOL;
Chris@17 1012 }
Chris@17 1013 }//end if
Chris@17 1014 }//end foreach
Chris@17 1015 }//end if
Chris@17 1016
Chris@17 1017 // Ignore patterns.
Chris@17 1018 foreach ($rule->{'exclude-pattern'} as $pattern) {
Chris@17 1019 if ($this->shouldProcessElement($pattern) === false) {
Chris@17 1020 continue;
Chris@17 1021 }
Chris@17 1022
Chris@17 1023 if (isset($this->ignorePatterns[$code]) === false) {
Chris@17 1024 $this->ignorePatterns[$code] = [];
Chris@17 1025 }
Chris@17 1026
Chris@17 1027 if (isset($pattern['type']) === false) {
Chris@17 1028 $pattern['type'] = 'absolute';
Chris@17 1029 }
Chris@17 1030
Chris@17 1031 $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type'];
Chris@17 1032 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 1033 echo str_repeat("\t", $depth);
Chris@17 1034 echo "\t\t=> added rule-specific ".(string) $pattern['type'].' ignore pattern';
Chris@17 1035 if ($code !== $ref) {
Chris@17 1036 echo " for $code";
Chris@17 1037 }
Chris@17 1038
Chris@17 1039 echo ': '.(string) $pattern.PHP_EOL;
Chris@17 1040 }
Chris@17 1041 }//end foreach
Chris@17 1042
Chris@17 1043 // Include patterns.
Chris@17 1044 foreach ($rule->{'include-pattern'} as $pattern) {
Chris@17 1045 if ($this->shouldProcessElement($pattern) === false) {
Chris@17 1046 continue;
Chris@17 1047 }
Chris@17 1048
Chris@17 1049 if (isset($this->includePatterns[$code]) === false) {
Chris@17 1050 $this->includePatterns[$code] = [];
Chris@17 1051 }
Chris@17 1052
Chris@17 1053 if (isset($pattern['type']) === false) {
Chris@17 1054 $pattern['type'] = 'absolute';
Chris@17 1055 }
Chris@17 1056
Chris@17 1057 $this->includePatterns[$code][(string) $pattern] = (string) $pattern['type'];
Chris@17 1058 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 1059 echo str_repeat("\t", $depth);
Chris@17 1060 echo "\t\t=> added rule-specific ".(string) $pattern['type'].' include pattern';
Chris@17 1061 if ($code !== $ref) {
Chris@17 1062 echo " for $code";
Chris@17 1063 }
Chris@17 1064
Chris@17 1065 echo ': '.(string) $pattern.PHP_EOL;
Chris@17 1066 }
Chris@17 1067 }//end foreach
Chris@17 1068 }//end foreach
Chris@17 1069
Chris@17 1070 }//end processRule()
Chris@17 1071
Chris@17 1072
Chris@17 1073 /**
Chris@17 1074 * Determine if an element should be processed or ignored.
Chris@17 1075 *
Chris@17 1076 * @param SimpleXMLElement $element An object from a ruleset XML file.
Chris@17 1077 *
Chris@17 1078 * @return bool
Chris@17 1079 */
Chris@17 1080 private function shouldProcessElement($element)
Chris@17 1081 {
Chris@17 1082 if (isset($element['phpcbf-only']) === false
Chris@17 1083 && isset($element['phpcs-only']) === false
Chris@17 1084 ) {
Chris@17 1085 // No exceptions are being made.
Chris@17 1086 return true;
Chris@17 1087 }
Chris@17 1088
Chris@17 1089 if (PHP_CODESNIFFER_CBF === true
Chris@17 1090 && isset($element['phpcbf-only']) === true
Chris@17 1091 && (string) $element['phpcbf-only'] === 'true'
Chris@17 1092 ) {
Chris@17 1093 return true;
Chris@17 1094 }
Chris@17 1095
Chris@17 1096 if (PHP_CODESNIFFER_CBF === false
Chris@17 1097 && isset($element['phpcs-only']) === true
Chris@17 1098 && (string) $element['phpcs-only'] === 'true'
Chris@17 1099 ) {
Chris@17 1100 return true;
Chris@17 1101 }
Chris@17 1102
Chris@17 1103 return false;
Chris@17 1104
Chris@17 1105 }//end shouldProcessElement()
Chris@17 1106
Chris@17 1107
Chris@17 1108 /**
Chris@17 1109 * Loads and stores sniffs objects used for sniffing files.
Chris@17 1110 *
Chris@17 1111 * @param array $files Paths to the sniff files to register.
Chris@17 1112 * @param array $restrictions The sniff class names to restrict the allowed
Chris@17 1113 * listeners to.
Chris@17 1114 * @param array $exclusions The sniff class names to exclude from the
Chris@17 1115 * listeners list.
Chris@17 1116 *
Chris@17 1117 * @return void
Chris@17 1118 */
Chris@17 1119 public function registerSniffs($files, $restrictions, $exclusions)
Chris@17 1120 {
Chris@17 1121 $listeners = [];
Chris@17 1122
Chris@17 1123 foreach ($files as $file) {
Chris@17 1124 // Work out where the position of /StandardName/Sniffs/... is
Chris@17 1125 // so we can determine what the class will be called.
Chris@17 1126 $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
Chris@17 1127 if ($sniffPos === false) {
Chris@17 1128 continue;
Chris@17 1129 }
Chris@17 1130
Chris@17 1131 $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
Chris@17 1132 if ($slashPos === false) {
Chris@17 1133 continue;
Chris@17 1134 }
Chris@17 1135
Chris@17 1136 $className = Autoload::loadFile($file);
Chris@17 1137 $compareName = Util\Common::cleanSniffClass($className);
Chris@17 1138
Chris@17 1139 // If they have specified a list of sniffs to restrict to, check
Chris@17 1140 // to see if this sniff is allowed.
Chris@17 1141 if (empty($restrictions) === false
Chris@17 1142 && isset($restrictions[$compareName]) === false
Chris@17 1143 ) {
Chris@17 1144 continue;
Chris@17 1145 }
Chris@17 1146
Chris@17 1147 // If they have specified a list of sniffs to exclude, check
Chris@17 1148 // to see if this sniff is allowed.
Chris@17 1149 if (empty($exclusions) === false
Chris@17 1150 && isset($exclusions[$compareName]) === true
Chris@17 1151 ) {
Chris@17 1152 continue;
Chris@17 1153 }
Chris@17 1154
Chris@17 1155 // Skip abstract classes.
Chris@17 1156 $reflection = new \ReflectionClass($className);
Chris@17 1157 if ($reflection->isAbstract() === true) {
Chris@17 1158 continue;
Chris@17 1159 }
Chris@17 1160
Chris@17 1161 $listeners[$className] = $className;
Chris@17 1162
Chris@17 1163 if (PHP_CODESNIFFER_VERBOSITY > 2) {
Chris@17 1164 echo "Registered $className".PHP_EOL;
Chris@17 1165 }
Chris@17 1166 }//end foreach
Chris@17 1167
Chris@17 1168 $this->sniffs = $listeners;
Chris@17 1169
Chris@17 1170 }//end registerSniffs()
Chris@17 1171
Chris@17 1172
Chris@17 1173 /**
Chris@17 1174 * Populates the array of PHP_CodeSniffer_Sniff's for this file.
Chris@17 1175 *
Chris@17 1176 * @return void
Chris@17 1177 * @throws RuntimeException If sniff registration fails.
Chris@17 1178 */
Chris@17 1179 public function populateTokenListeners()
Chris@17 1180 {
Chris@17 1181 // Construct a list of listeners indexed by token being listened for.
Chris@17 1182 $this->tokenListeners = [];
Chris@17 1183
Chris@17 1184 foreach ($this->sniffs as $sniffClass => $sniffObject) {
Chris@17 1185 $this->sniffs[$sniffClass] = null;
Chris@17 1186 $this->sniffs[$sniffClass] = new $sniffClass();
Chris@17 1187
Chris@17 1188 $sniffCode = Util\Common::getSniffCode($sniffClass);
Chris@17 1189 $this->sniffCodes[$sniffCode] = $sniffClass;
Chris@17 1190
Chris@17 1191 // Set custom properties.
Chris@17 1192 if (isset($this->ruleset[$sniffCode]['properties']) === true) {
Chris@17 1193 foreach ($this->ruleset[$sniffCode]['properties'] as $name => $value) {
Chris@17 1194 $this->setSniffProperty($sniffClass, $name, $value);
Chris@17 1195 }
Chris@17 1196 }
Chris@17 1197
Chris@17 1198 $tokenizers = [];
Chris@17 1199 $vars = get_class_vars($sniffClass);
Chris@17 1200 if (isset($vars['supportedTokenizers']) === true) {
Chris@17 1201 foreach ($vars['supportedTokenizers'] as $tokenizer) {
Chris@17 1202 $tokenizers[$tokenizer] = $tokenizer;
Chris@17 1203 }
Chris@17 1204 } else {
Chris@17 1205 $tokenizers = ['PHP' => 'PHP'];
Chris@17 1206 }
Chris@17 1207
Chris@17 1208 $tokens = $this->sniffs[$sniffClass]->register();
Chris@17 1209 if (is_array($tokens) === false) {
Chris@17 1210 $msg = "Sniff $sniffClass register() method must return an array";
Chris@17 1211 throw new RuntimeException($msg);
Chris@17 1212 }
Chris@17 1213
Chris@17 1214 $ignorePatterns = [];
Chris@17 1215 $patterns = $this->getIgnorePatterns($sniffCode);
Chris@17 1216 foreach ($patterns as $pattern => $type) {
Chris@17 1217 $replacements = [
Chris@17 1218 '\\,' => ',',
Chris@17 1219 '*' => '.*',
Chris@17 1220 ];
Chris@17 1221
Chris@17 1222 $ignorePatterns[] = strtr($pattern, $replacements);
Chris@17 1223 }
Chris@17 1224
Chris@17 1225 $includePatterns = [];
Chris@17 1226 $patterns = $this->getIncludePatterns($sniffCode);
Chris@17 1227 foreach ($patterns as $pattern => $type) {
Chris@17 1228 $replacements = [
Chris@17 1229 '\\,' => ',',
Chris@17 1230 '*' => '.*',
Chris@17 1231 ];
Chris@17 1232
Chris@17 1233 $includePatterns[] = strtr($pattern, $replacements);
Chris@17 1234 }
Chris@17 1235
Chris@17 1236 foreach ($tokens as $token) {
Chris@17 1237 if (isset($this->tokenListeners[$token]) === false) {
Chris@17 1238 $this->tokenListeners[$token] = [];
Chris@17 1239 }
Chris@17 1240
Chris@17 1241 if (isset($this->tokenListeners[$token][$sniffClass]) === false) {
Chris@17 1242 $this->tokenListeners[$token][$sniffClass] = [
Chris@17 1243 'class' => $sniffClass,
Chris@17 1244 'source' => $sniffCode,
Chris@17 1245 'tokenizers' => $tokenizers,
Chris@17 1246 'ignore' => $ignorePatterns,
Chris@17 1247 'include' => $includePatterns,
Chris@17 1248 ];
Chris@17 1249 }
Chris@17 1250 }
Chris@17 1251 }//end foreach
Chris@17 1252
Chris@17 1253 }//end populateTokenListeners()
Chris@17 1254
Chris@17 1255
Chris@17 1256 /**
Chris@17 1257 * Set a single property for a sniff.
Chris@17 1258 *
Chris@17 1259 * @param string $sniffClass The class name of the sniff.
Chris@17 1260 * @param string $name The name of the property to change.
Chris@17 1261 * @param string $value The new value of the property.
Chris@17 1262 *
Chris@17 1263 * @return void
Chris@17 1264 */
Chris@17 1265 public function setSniffProperty($sniffClass, $name, $value)
Chris@17 1266 {
Chris@17 1267 // Setting a property for a sniff we are not using.
Chris@17 1268 if (isset($this->sniffs[$sniffClass]) === false) {
Chris@17 1269 return;
Chris@17 1270 }
Chris@17 1271
Chris@17 1272 $name = trim($name);
Chris@17 1273 if (is_string($value) === true) {
Chris@17 1274 $value = trim($value);
Chris@17 1275 }
Chris@17 1276
Chris@17 1277 if ($value === '') {
Chris@17 1278 $value = null;
Chris@17 1279 }
Chris@17 1280
Chris@17 1281 // Special case for booleans.
Chris@17 1282 if ($value === 'true') {
Chris@17 1283 $value = true;
Chris@17 1284 } else if ($value === 'false') {
Chris@17 1285 $value = false;
Chris@17 1286 } else if (substr($name, -2) === '[]') {
Chris@17 1287 $name = substr($name, 0, -2);
Chris@17 1288 $values = [];
Chris@17 1289 if ($value !== null) {
Chris@17 1290 foreach (explode(',', $value) as $val) {
Chris@17 1291 list($k, $v) = explode('=>', $val.'=>');
Chris@17 1292 if ($v !== '') {
Chris@17 1293 $values[trim($k)] = trim($v);
Chris@17 1294 } else {
Chris@17 1295 $values[] = trim($k);
Chris@17 1296 }
Chris@17 1297 }
Chris@17 1298 }
Chris@17 1299
Chris@17 1300 $value = $values;
Chris@17 1301 }
Chris@17 1302
Chris@17 1303 $this->sniffs[$sniffClass]->$name = $value;
Chris@17 1304
Chris@17 1305 }//end setSniffProperty()
Chris@17 1306
Chris@17 1307
Chris@17 1308 /**
Chris@17 1309 * Gets the array of ignore patterns.
Chris@17 1310 *
Chris@17 1311 * Optionally takes a listener to get ignore patterns specified
Chris@17 1312 * for that sniff only.
Chris@17 1313 *
Chris@17 1314 * @param string $listener The listener to get patterns for. If NULL, all
Chris@17 1315 * patterns are returned.
Chris@17 1316 *
Chris@17 1317 * @return array
Chris@17 1318 */
Chris@17 1319 public function getIgnorePatterns($listener=null)
Chris@17 1320 {
Chris@17 1321 if ($listener === null) {
Chris@17 1322 return $this->ignorePatterns;
Chris@17 1323 }
Chris@17 1324
Chris@17 1325 if (isset($this->ignorePatterns[$listener]) === true) {
Chris@17 1326 return $this->ignorePatterns[$listener];
Chris@17 1327 }
Chris@17 1328
Chris@17 1329 return [];
Chris@17 1330
Chris@17 1331 }//end getIgnorePatterns()
Chris@17 1332
Chris@17 1333
Chris@17 1334 /**
Chris@17 1335 * Gets the array of include patterns.
Chris@17 1336 *
Chris@17 1337 * Optionally takes a listener to get include patterns specified
Chris@17 1338 * for that sniff only.
Chris@17 1339 *
Chris@17 1340 * @param string $listener The listener to get patterns for. If NULL, all
Chris@17 1341 * patterns are returned.
Chris@17 1342 *
Chris@17 1343 * @return array
Chris@17 1344 */
Chris@17 1345 public function getIncludePatterns($listener=null)
Chris@17 1346 {
Chris@17 1347 if ($listener === null) {
Chris@17 1348 return $this->includePatterns;
Chris@17 1349 }
Chris@17 1350
Chris@17 1351 if (isset($this->includePatterns[$listener]) === true) {
Chris@17 1352 return $this->includePatterns[$listener];
Chris@17 1353 }
Chris@17 1354
Chris@17 1355 return [];
Chris@17 1356
Chris@17 1357 }//end getIncludePatterns()
Chris@17 1358
Chris@17 1359
Chris@17 1360 }//end class