annotate vendor/squizlabs/php_codesniffer/src/Ruleset.php @ 5:12f9dff5fda9 tip

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