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

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