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

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents
children af1871eacc83
rev   line source
Chris@17 1 <?php
Chris@17 2 /**
Chris@17 3 * Responsible for running PHPCS and PHPCBF.
Chris@17 4 *
Chris@17 5 * After creating an object of this class, you probably just want to
Chris@17 6 * call runPHPCS() or runPHPCBF().
Chris@17 7 *
Chris@17 8 * @author Greg Sherwood <gsherwood@squiz.net>
Chris@17 9 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
Chris@17 10 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
Chris@17 11 */
Chris@17 12
Chris@17 13 namespace PHP_CodeSniffer;
Chris@17 14
Chris@17 15 use PHP_CodeSniffer\Files\FileList;
Chris@17 16 use PHP_CodeSniffer\Files\File;
Chris@17 17 use PHP_CodeSniffer\Files\DummyFile;
Chris@17 18 use PHP_CodeSniffer\Util\Cache;
Chris@17 19 use PHP_CodeSniffer\Util\Common;
Chris@17 20 use PHP_CodeSniffer\Util\Standards;
Chris@17 21 use PHP_CodeSniffer\Exceptions\RuntimeException;
Chris@17 22 use PHP_CodeSniffer\Exceptions\DeepExitException;
Chris@17 23
Chris@17 24 class Runner
Chris@17 25 {
Chris@17 26
Chris@17 27 /**
Chris@17 28 * The config data for the run.
Chris@17 29 *
Chris@17 30 * @var \PHP_CodeSniffer\Config
Chris@17 31 */
Chris@17 32 public $config = null;
Chris@17 33
Chris@17 34 /**
Chris@17 35 * The ruleset used for the run.
Chris@17 36 *
Chris@17 37 * @var \PHP_CodeSniffer\Ruleset
Chris@17 38 */
Chris@17 39 public $ruleset = null;
Chris@17 40
Chris@17 41 /**
Chris@17 42 * The reporter used for generating reports after the run.
Chris@17 43 *
Chris@17 44 * @var \PHP_CodeSniffer\Reporter
Chris@17 45 */
Chris@17 46 public $reporter = null;
Chris@17 47
Chris@17 48
Chris@17 49 /**
Chris@17 50 * Run the PHPCS script.
Chris@17 51 *
Chris@17 52 * @return array
Chris@17 53 */
Chris@17 54 public function runPHPCS()
Chris@17 55 {
Chris@17 56 try {
Chris@17 57 Util\Timing::startTiming();
Chris@17 58 Runner::checkRequirements();
Chris@17 59
Chris@17 60 if (defined('PHP_CODESNIFFER_CBF') === false) {
Chris@17 61 define('PHP_CODESNIFFER_CBF', false);
Chris@17 62 }
Chris@17 63
Chris@17 64 // Creating the Config object populates it with all required settings
Chris@17 65 // based on the CLI arguments provided to the script and any config
Chris@17 66 // values the user has set.
Chris@17 67 $this->config = new Config();
Chris@17 68
Chris@17 69 // Init the run and load the rulesets to set additional config vars.
Chris@17 70 $this->init();
Chris@17 71
Chris@17 72 // Print a list of sniffs in each of the supplied standards.
Chris@17 73 // We fudge the config here so that each standard is explained in isolation.
Chris@17 74 if ($this->config->explain === true) {
Chris@17 75 $standards = $this->config->standards;
Chris@17 76 foreach ($standards as $standard) {
Chris@17 77 $this->config->standards = [$standard];
Chris@17 78 $ruleset = new Ruleset($this->config);
Chris@17 79 $ruleset->explain();
Chris@17 80 }
Chris@17 81
Chris@17 82 return 0;
Chris@17 83 }
Chris@17 84
Chris@17 85 // Generate documentation for each of the supplied standards.
Chris@17 86 if ($this->config->generator !== null) {
Chris@17 87 $standards = $this->config->standards;
Chris@17 88 foreach ($standards as $standard) {
Chris@17 89 $this->config->standards = [$standard];
Chris@17 90 $ruleset = new Ruleset($this->config);
Chris@17 91 $class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator;
Chris@17 92 $generator = new $class($ruleset);
Chris@17 93 $generator->generate();
Chris@17 94 }
Chris@17 95
Chris@17 96 return 0;
Chris@17 97 }
Chris@17 98
Chris@17 99 // Other report formats don't really make sense in interactive mode
Chris@17 100 // so we hard-code the full report here and when outputting.
Chris@17 101 // We also ensure parallel processing is off because we need to do one file at a time.
Chris@17 102 if ($this->config->interactive === true) {
Chris@17 103 $this->config->reports = ['full' => null];
Chris@17 104 $this->config->parallel = 1;
Chris@17 105 $this->config->showProgress = false;
Chris@17 106 }
Chris@17 107
Chris@17 108 // Disable caching if we are processing STDIN as we can't be 100%
Chris@17 109 // sure where the file came from or if it will change in the future.
Chris@17 110 if ($this->config->stdin === true) {
Chris@17 111 $this->config->cache = false;
Chris@17 112 }
Chris@17 113
Chris@17 114 $numErrors = $this->run();
Chris@17 115
Chris@17 116 // Print all the reports for this run.
Chris@17 117 $toScreen = $this->reporter->printReports();
Chris@17 118
Chris@17 119 // Only print timer output if no reports were
Chris@17 120 // printed to the screen so we don't put additional output
Chris@17 121 // in something like an XML report. If we are printing to screen,
Chris@17 122 // the report types would have already worked out who should
Chris@17 123 // print the timer info.
Chris@17 124 if ($this->config->interactive === false
Chris@17 125 && ($toScreen === false
Chris@17 126 || (($this->reporter->totalErrors + $this->reporter->totalWarnings) === 0 && $this->config->showProgress === true))
Chris@17 127 ) {
Chris@17 128 Util\Timing::printRunTime();
Chris@17 129 }
Chris@17 130 } catch (DeepExitException $e) {
Chris@17 131 echo $e->getMessage();
Chris@17 132 return $e->getCode();
Chris@17 133 }//end try
Chris@17 134
Chris@17 135 if ($numErrors === 0) {
Chris@17 136 // No errors found.
Chris@17 137 return 0;
Chris@17 138 } else if ($this->reporter->totalFixable === 0) {
Chris@17 139 // Errors found, but none of them can be fixed by PHPCBF.
Chris@17 140 return 1;
Chris@17 141 } else {
Chris@17 142 // Errors found, and some can be fixed by PHPCBF.
Chris@17 143 return 2;
Chris@17 144 }
Chris@17 145
Chris@17 146 }//end runPHPCS()
Chris@17 147
Chris@17 148
Chris@17 149 /**
Chris@17 150 * Run the PHPCBF script.
Chris@17 151 *
Chris@17 152 * @return array
Chris@17 153 */
Chris@17 154 public function runPHPCBF()
Chris@17 155 {
Chris@17 156 if (defined('PHP_CODESNIFFER_CBF') === false) {
Chris@17 157 define('PHP_CODESNIFFER_CBF', true);
Chris@17 158 }
Chris@17 159
Chris@17 160 try {
Chris@17 161 Util\Timing::startTiming();
Chris@17 162 Runner::checkRequirements();
Chris@17 163
Chris@17 164 // Creating the Config object populates it with all required settings
Chris@17 165 // based on the CLI arguments provided to the script and any config
Chris@17 166 // values the user has set.
Chris@17 167 $this->config = new Config();
Chris@17 168
Chris@17 169 // When processing STDIN, we can't output anything to the screen
Chris@17 170 // or it will end up mixed in with the file output.
Chris@17 171 if ($this->config->stdin === true) {
Chris@17 172 $this->config->verbosity = 0;
Chris@17 173 }
Chris@17 174
Chris@17 175 // Init the run and load the rulesets to set additional config vars.
Chris@17 176 $this->init();
Chris@17 177
Chris@17 178 // When processing STDIN, we only process one file at a time and
Chris@17 179 // we don't process all the way through, so we can't use the parallel
Chris@17 180 // running system.
Chris@17 181 if ($this->config->stdin === true) {
Chris@17 182 $this->config->parallel = 1;
Chris@17 183 }
Chris@17 184
Chris@17 185 // Override some of the command line settings that might break the fixes.
Chris@17 186 $this->config->generator = null;
Chris@17 187 $this->config->explain = false;
Chris@17 188 $this->config->interactive = false;
Chris@17 189 $this->config->cache = false;
Chris@17 190 $this->config->showSources = false;
Chris@17 191 $this->config->recordErrors = false;
Chris@17 192 $this->config->reportFile = null;
Chris@17 193 $this->config->reports = ['cbf' => null];
Chris@17 194
Chris@17 195 // If a standard tries to set command line arguments itself, some
Chris@17 196 // may be blocked because PHPCBF is running, so stop the script
Chris@17 197 // dying if any are found.
Chris@17 198 $this->config->dieOnUnknownArg = false;
Chris@17 199
Chris@17 200 $this->run();
Chris@17 201 $this->reporter->printReports();
Chris@17 202
Chris@17 203 echo PHP_EOL;
Chris@17 204 Util\Timing::printRunTime();
Chris@17 205 } catch (DeepExitException $e) {
Chris@17 206 echo $e->getMessage();
Chris@17 207 return $e->getCode();
Chris@17 208 }//end try
Chris@17 209
Chris@17 210 if ($this->reporter->totalFixed === 0) {
Chris@17 211 // Nothing was fixed by PHPCBF.
Chris@17 212 if ($this->reporter->totalFixable === 0) {
Chris@17 213 // Nothing found that could be fixed.
Chris@17 214 return 0;
Chris@17 215 } else {
Chris@17 216 // Something failed to fix.
Chris@17 217 return 2;
Chris@17 218 }
Chris@17 219 }
Chris@17 220
Chris@17 221 if ($this->reporter->totalFixable === 0) {
Chris@17 222 // PHPCBF fixed all fixable errors.
Chris@17 223 return 1;
Chris@17 224 }
Chris@17 225
Chris@17 226 // PHPCBF fixed some fixable errors, but others failed to fix.
Chris@17 227 return 2;
Chris@17 228
Chris@17 229 }//end runPHPCBF()
Chris@17 230
Chris@17 231
Chris@17 232 /**
Chris@17 233 * Exits if the minimum requirements of PHP_CodSniffer are not met.
Chris@17 234 *
Chris@17 235 * @return array
Chris@17 236 */
Chris@17 237 public function checkRequirements()
Chris@17 238 {
Chris@17 239 // Check the PHP version.
Chris@17 240 if (PHP_VERSION_ID < 50400) {
Chris@17 241 $error = 'ERROR: PHP_CodeSniffer requires PHP version 5.4.0 or greater.'.PHP_EOL;
Chris@17 242 throw new DeepExitException($error, 3);
Chris@17 243 }
Chris@17 244
Chris@17 245 if (extension_loaded('tokenizer') === false) {
Chris@17 246 $error = 'ERROR: PHP_CodeSniffer requires the tokenizer extension to be enabled.'.PHP_EOL;
Chris@17 247 throw new DeepExitException($error, 3);
Chris@17 248 }
Chris@17 249
Chris@17 250 }//end checkRequirements()
Chris@17 251
Chris@17 252
Chris@17 253 /**
Chris@17 254 * Init the rulesets and other high-level settings.
Chris@17 255 *
Chris@17 256 * @return void
Chris@17 257 */
Chris@17 258 public function init()
Chris@17 259 {
Chris@17 260 if (defined('PHP_CODESNIFFER_CBF') === false) {
Chris@17 261 define('PHP_CODESNIFFER_CBF', false);
Chris@17 262 }
Chris@17 263
Chris@17 264 // Ensure this option is enabled or else line endings will not always
Chris@17 265 // be detected properly for files created on a Mac with the /r line ending.
Chris@17 266 ini_set('auto_detect_line_endings', true);
Chris@17 267
Chris@17 268 // Check that the standards are valid.
Chris@17 269 foreach ($this->config->standards as $standard) {
Chris@17 270 if (Util\Standards::isInstalledStandard($standard) === false) {
Chris@17 271 // They didn't select a valid coding standard, so help them
Chris@17 272 // out by letting them know which standards are installed.
Chris@17 273 $error = 'ERROR: the "'.$standard.'" coding standard is not installed. ';
Chris@17 274 ob_start();
Chris@17 275 Util\Standards::printInstalledStandards();
Chris@17 276 $error .= ob_get_contents();
Chris@17 277 ob_end_clean();
Chris@17 278 throw new DeepExitException($error, 3);
Chris@17 279 }
Chris@17 280 }
Chris@17 281
Chris@17 282 // Saves passing the Config object into other objects that only need
Chris@17 283 // the verbostity flag for deubg output.
Chris@17 284 if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
Chris@17 285 define('PHP_CODESNIFFER_VERBOSITY', $this->config->verbosity);
Chris@17 286 }
Chris@17 287
Chris@17 288 // Create this class so it is autoloaded and sets up a bunch
Chris@17 289 // of PHP_CodeSniffer-specific token type constants.
Chris@17 290 $tokens = new Util\Tokens();
Chris@17 291
Chris@17 292 // Allow autoloading of custom files inside installed standards.
Chris@17 293 $installedStandards = Standards::getInstalledStandardDetails();
Chris@17 294 foreach ($installedStandards as $name => $details) {
Chris@17 295 Autoload::addSearchPath($details['path'], $details['namespace']);
Chris@17 296 }
Chris@17 297
Chris@17 298 // The ruleset contains all the information about how the files
Chris@17 299 // should be checked and/or fixed.
Chris@17 300 try {
Chris@17 301 $this->ruleset = new Ruleset($this->config);
Chris@17 302 } catch (RuntimeException $e) {
Chris@17 303 $error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL;
Chris@17 304 $error .= $this->config->printShortUsage(true);
Chris@17 305 throw new DeepExitException($error, 3);
Chris@17 306 }
Chris@17 307
Chris@17 308 }//end init()
Chris@17 309
Chris@17 310
Chris@17 311 /**
Chris@17 312 * Performs the run.
Chris@17 313 *
Chris@17 314 * @return int The number of errors and warnings found.
Chris@17 315 */
Chris@17 316 private function run()
Chris@17 317 {
Chris@17 318 // The class that manages all reporters for the run.
Chris@17 319 $this->reporter = new Reporter($this->config);
Chris@17 320
Chris@17 321 // Include bootstrap files.
Chris@17 322 foreach ($this->config->bootstrap as $bootstrap) {
Chris@17 323 include $bootstrap;
Chris@17 324 }
Chris@17 325
Chris@17 326 if ($this->config->stdin === true) {
Chris@17 327 $fileContents = $this->config->stdinContent;
Chris@17 328 if ($fileContents === null) {
Chris@17 329 $handle = fopen('php://stdin', 'r');
Chris@17 330 stream_set_blocking($handle, true);
Chris@17 331 $fileContents = stream_get_contents($handle);
Chris@17 332 fclose($handle);
Chris@17 333 }
Chris@17 334
Chris@17 335 $todo = new FileList($this->config, $this->ruleset);
Chris@17 336 $dummy = new DummyFile($fileContents, $this->ruleset, $this->config);
Chris@17 337 $todo->addFile($dummy->path, $dummy);
Chris@17 338 } else {
Chris@17 339 if (empty($this->config->files) === true) {
Chris@17 340 $error = 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL;
Chris@17 341 $error .= $this->config->printShortUsage(true);
Chris@17 342 throw new DeepExitException($error, 3);
Chris@17 343 }
Chris@17 344
Chris@17 345 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 346 echo 'Creating file list... ';
Chris@17 347 }
Chris@17 348
Chris@17 349 $todo = new FileList($this->config, $this->ruleset);
Chris@17 350
Chris@17 351 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 352 $numFiles = count($todo);
Chris@17 353 echo "DONE ($numFiles files in queue)".PHP_EOL;
Chris@17 354 }
Chris@17 355
Chris@17 356 if ($this->config->cache === true) {
Chris@17 357 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 358 echo 'Loading cache... ';
Chris@17 359 }
Chris@17 360
Chris@17 361 Cache::load($this->ruleset, $this->config);
Chris@17 362
Chris@17 363 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 364 $size = Cache::getSize();
Chris@17 365 echo "DONE ($size files in cache)".PHP_EOL;
Chris@17 366 }
Chris@17 367 }
Chris@17 368 }//end if
Chris@17 369
Chris@17 370 // Turn all sniff errors into exceptions.
Chris@17 371 set_error_handler([$this, 'handleErrors']);
Chris@17 372
Chris@17 373 // If verbosity is too high, turn off parallelism so the
Chris@17 374 // debug output is clean.
Chris@17 375 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 376 $this->config->parallel = 1;
Chris@17 377 }
Chris@17 378
Chris@17 379 // If the PCNTL extension isn't installed, we can't fork.
Chris@17 380 if (function_exists('pcntl_fork') === false) {
Chris@17 381 $this->config->parallel = 1;
Chris@17 382 }
Chris@17 383
Chris@17 384 $lastDir = '';
Chris@17 385 $numFiles = count($todo);
Chris@17 386
Chris@17 387 if ($this->config->parallel === 1) {
Chris@17 388 // Running normally.
Chris@17 389 $numProcessed = 0;
Chris@17 390 foreach ($todo as $path => $file) {
Chris@17 391 if ($file->ignored === false) {
Chris@17 392 $currDir = dirname($path);
Chris@17 393 if ($lastDir !== $currDir) {
Chris@17 394 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 395 echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
Chris@17 396 }
Chris@17 397
Chris@17 398 $lastDir = $currDir;
Chris@17 399 }
Chris@17 400
Chris@17 401 $this->processFile($file);
Chris@17 402 } else if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 403 echo 'Skipping '.basename($file->path).PHP_EOL;
Chris@17 404 }
Chris@17 405
Chris@17 406 $numProcessed++;
Chris@17 407 $this->printProgress($file, $numFiles, $numProcessed);
Chris@17 408 }
Chris@17 409 } else {
Chris@17 410 // Batching and forking.
Chris@17 411 $childProcs = [];
Chris@17 412 $numPerBatch = ceil($numFiles / $this->config->parallel);
Chris@17 413
Chris@17 414 for ($batch = 0; $batch < $this->config->parallel; $batch++) {
Chris@17 415 $startAt = ($batch * $numPerBatch);
Chris@17 416 if ($startAt >= $numFiles) {
Chris@17 417 break;
Chris@17 418 }
Chris@17 419
Chris@17 420 $endAt = ($startAt + $numPerBatch);
Chris@17 421 if ($endAt > $numFiles) {
Chris@17 422 $endAt = $numFiles;
Chris@17 423 }
Chris@17 424
Chris@17 425 $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child');
Chris@17 426 $pid = pcntl_fork();
Chris@17 427 if ($pid === -1) {
Chris@17 428 throw new RuntimeException('Failed to create child process');
Chris@17 429 } else if ($pid !== 0) {
Chris@17 430 $childProcs[] = [
Chris@17 431 'pid' => $pid,
Chris@17 432 'out' => $childOutFilename,
Chris@17 433 ];
Chris@17 434 } else {
Chris@17 435 // Move forward to the start of the batch.
Chris@17 436 $todo->rewind();
Chris@17 437 for ($i = 0; $i < $startAt; $i++) {
Chris@17 438 $todo->next();
Chris@17 439 }
Chris@17 440
Chris@17 441 // Reset the reporter to make sure only figures from this
Chris@17 442 // file batch are recorded.
Chris@17 443 $this->reporter->totalFiles = 0;
Chris@17 444 $this->reporter->totalErrors = 0;
Chris@17 445 $this->reporter->totalWarnings = 0;
Chris@17 446 $this->reporter->totalFixable = 0;
Chris@17 447 $this->reporter->totalFixed = 0;
Chris@17 448
Chris@17 449 // Process the files.
Chris@17 450 $pathsProcessed = [];
Chris@17 451 ob_start();
Chris@17 452 for ($i = $startAt; $i < $endAt; $i++) {
Chris@17 453 $path = $todo->key();
Chris@17 454 $file = $todo->current();
Chris@17 455
Chris@17 456 if ($file->ignored === true) {
Chris@17 457 continue;
Chris@17 458 }
Chris@17 459
Chris@17 460 $currDir = dirname($path);
Chris@17 461 if ($lastDir !== $currDir) {
Chris@17 462 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 463 echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
Chris@17 464 }
Chris@17 465
Chris@17 466 $lastDir = $currDir;
Chris@17 467 }
Chris@17 468
Chris@17 469 $this->processFile($file);
Chris@17 470
Chris@17 471 $pathsProcessed[] = $path;
Chris@17 472 $todo->next();
Chris@17 473 }//end for
Chris@17 474
Chris@17 475 $debugOutput = ob_get_contents();
Chris@17 476 ob_end_clean();
Chris@17 477
Chris@17 478 // Write information about the run to the filesystem
Chris@17 479 // so it can be picked up by the main process.
Chris@17 480 $childOutput = [
Chris@17 481 'totalFiles' => $this->reporter->totalFiles,
Chris@17 482 'totalErrors' => $this->reporter->totalErrors,
Chris@17 483 'totalWarnings' => $this->reporter->totalWarnings,
Chris@17 484 'totalFixable' => $this->reporter->totalFixable,
Chris@17 485 'totalFixed' => $this->reporter->totalFixed,
Chris@17 486 ];
Chris@17 487
Chris@17 488 $output = '<'.'?php'."\n".' $childOutput = ';
Chris@17 489 $output .= var_export($childOutput, true);
Chris@17 490 $output .= ";\n\$debugOutput = ";
Chris@17 491 $output .= var_export($debugOutput, true);
Chris@17 492
Chris@17 493 if ($this->config->cache === true) {
Chris@17 494 $childCache = [];
Chris@17 495 foreach ($pathsProcessed as $path) {
Chris@17 496 $childCache[$path] = Cache::get($path);
Chris@17 497 }
Chris@17 498
Chris@17 499 $output .= ";\n\$childCache = ";
Chris@17 500 $output .= var_export($childCache, true);
Chris@17 501 }
Chris@17 502
Chris@17 503 $output .= ";\n?".'>';
Chris@17 504 file_put_contents($childOutFilename, $output);
Chris@17 505 exit($pid);
Chris@17 506 }//end if
Chris@17 507 }//end for
Chris@17 508
Chris@17 509 $this->processChildProcs($childProcs);
Chris@17 510 }//end if
Chris@17 511
Chris@17 512 restore_error_handler();
Chris@17 513
Chris@17 514 if (PHP_CODESNIFFER_VERBOSITY === 0
Chris@17 515 && $this->config->interactive === false
Chris@17 516 && $this->config->showProgress === true
Chris@17 517 ) {
Chris@17 518 echo PHP_EOL.PHP_EOL;
Chris@17 519 }
Chris@17 520
Chris@17 521 if ($this->config->cache === true) {
Chris@17 522 Cache::save();
Chris@17 523 }
Chris@17 524
Chris@17 525 $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit');
Chris@17 526 $ignoreErrors = Config::getConfigData('ignore_errors_on_exit');
Chris@17 527
Chris@17 528 $return = ($this->reporter->totalErrors + $this->reporter->totalWarnings);
Chris@17 529 if ($ignoreErrors !== null) {
Chris@17 530 $ignoreErrors = (bool) $ignoreErrors;
Chris@17 531 if ($ignoreErrors === true) {
Chris@17 532 $return -= $this->reporter->totalErrors;
Chris@17 533 }
Chris@17 534 }
Chris@17 535
Chris@17 536 if ($ignoreWarnings !== null) {
Chris@17 537 $ignoreWarnings = (bool) $ignoreWarnings;
Chris@17 538 if ($ignoreWarnings === true) {
Chris@17 539 $return -= $this->reporter->totalWarnings;
Chris@17 540 }
Chris@17 541 }
Chris@17 542
Chris@17 543 return $return;
Chris@17 544
Chris@17 545 }//end run()
Chris@17 546
Chris@17 547
Chris@17 548 /**
Chris@17 549 * Converts all PHP errors into exceptions.
Chris@17 550 *
Chris@17 551 * This method forces a sniff to stop processing if it is not
Chris@17 552 * able to handle a specific piece of code, instead of continuing
Chris@17 553 * and potentially getting into a loop.
Chris@17 554 *
Chris@17 555 * @param int $code The level of error raised.
Chris@17 556 * @param string $message The error message.
Chris@17 557 * @param string $file The path of the file that raised the error.
Chris@17 558 * @param int $line The line number the error was raised at.
Chris@17 559 *
Chris@17 560 * @return void
Chris@17 561 */
Chris@17 562 public function handleErrors($code, $message, $file, $line)
Chris@17 563 {
Chris@17 564 if ((error_reporting() & $code) === 0) {
Chris@17 565 // This type of error is being muted.
Chris@17 566 return true;
Chris@17 567 }
Chris@17 568
Chris@17 569 throw new RuntimeException("$message in $file on line $line");
Chris@17 570
Chris@17 571 }//end handleErrors()
Chris@17 572
Chris@17 573
Chris@17 574 /**
Chris@17 575 * Processes a single file, including checking and fixing.
Chris@17 576 *
Chris@17 577 * @param \PHP_CodeSniffer\Files\File $file The file to be processed.
Chris@17 578 *
Chris@17 579 * @return void
Chris@17 580 */
Chris@17 581 public function processFile($file)
Chris@17 582 {
Chris@17 583 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 584 $startTime = microtime(true);
Chris@17 585 echo 'Processing '.basename($file->path).' ';
Chris@17 586 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 587 echo PHP_EOL;
Chris@17 588 }
Chris@17 589 }
Chris@17 590
Chris@17 591 try {
Chris@17 592 $file->process();
Chris@17 593
Chris@17 594 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 595 $timeTaken = ((microtime(true) - $startTime) * 1000);
Chris@17 596 if ($timeTaken < 1000) {
Chris@17 597 $timeTaken = round($timeTaken);
Chris@17 598 echo "DONE in {$timeTaken}ms";
Chris@17 599 } else {
Chris@17 600 $timeTaken = round(($timeTaken / 1000), 2);
Chris@17 601 echo "DONE in $timeTaken secs";
Chris@17 602 }
Chris@17 603
Chris@17 604 if (PHP_CODESNIFFER_CBF === true) {
Chris@17 605 $errors = $file->getFixableCount();
Chris@17 606 echo " ($errors fixable violations)".PHP_EOL;
Chris@17 607 } else {
Chris@17 608 $errors = $file->getErrorCount();
Chris@17 609 $warnings = $file->getWarningCount();
Chris@17 610 echo " ($errors errors, $warnings warnings)".PHP_EOL;
Chris@17 611 }
Chris@17 612 }
Chris@17 613 } catch (\Exception $e) {
Chris@17 614 $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
Chris@17 615 $file->addErrorOnLine($error, 1, 'Internal.Exception');
Chris@17 616 }//end try
Chris@17 617
Chris@17 618 $this->reporter->cacheFileReport($file, $this->config);
Chris@17 619
Chris@17 620 if ($this->config->interactive === true) {
Chris@17 621 /*
Chris@17 622 Running interactively.
Chris@17 623 Print the error report for the current file and then wait for user input.
Chris@17 624 */
Chris@17 625
Chris@17 626 // Get current violations and then clear the list to make sure
Chris@17 627 // we only print violations for a single file each time.
Chris@17 628 $numErrors = null;
Chris@17 629 while ($numErrors !== 0) {
Chris@17 630 $numErrors = ($file->getErrorCount() + $file->getWarningCount());
Chris@17 631 if ($numErrors === 0) {
Chris@17 632 continue;
Chris@17 633 }
Chris@17 634
Chris@17 635 $this->reporter->printReport('full');
Chris@17 636
Chris@17 637 echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
Chris@17 638 $input = fgets(STDIN);
Chris@17 639 $input = trim($input);
Chris@17 640
Chris@17 641 switch ($input) {
Chris@17 642 case 's':
Chris@17 643 break(2);
Chris@17 644 case 'q':
Chris@17 645 throw new DeepExitException('', 0);
Chris@17 646 default:
Chris@17 647 // Repopulate the sniffs because some of them save their state
Chris@17 648 // and only clear it when the file changes, but we are rechecking
Chris@17 649 // the same file.
Chris@17 650 $file->ruleset->populateTokenListeners();
Chris@17 651 $file->reloadContent();
Chris@17 652 $file->process();
Chris@17 653 $this->reporter->cacheFileReport($file, $this->config);
Chris@17 654 break;
Chris@17 655 }
Chris@17 656 }//end while
Chris@17 657 }//end if
Chris@17 658
Chris@17 659 // Clean up the file to save (a lot of) memory.
Chris@17 660 $file->cleanUp();
Chris@17 661
Chris@17 662 }//end processFile()
Chris@17 663
Chris@17 664
Chris@17 665 /**
Chris@17 666 * Waits for child processes to complete and cleans up after them.
Chris@17 667 *
Chris@17 668 * The reporting information returned by each child process is merged
Chris@17 669 * into the main reporter class.
Chris@17 670 *
Chris@17 671 * @param array $childProcs An array of child processes to wait for.
Chris@17 672 *
Chris@17 673 * @return void
Chris@17 674 */
Chris@17 675 private function processChildProcs($childProcs)
Chris@17 676 {
Chris@17 677 $numProcessed = 0;
Chris@17 678 $totalBatches = count($childProcs);
Chris@17 679
Chris@17 680 while (count($childProcs) > 0) {
Chris@17 681 foreach ($childProcs as $key => $procData) {
Chris@17 682 $res = pcntl_waitpid($procData['pid'], $status, WNOHANG);
Chris@17 683 if ($res === $procData['pid']) {
Chris@17 684 if (file_exists($procData['out']) === true) {
Chris@17 685 include $procData['out'];
Chris@17 686 if (isset($childOutput) === true) {
Chris@17 687 $this->reporter->totalFiles += $childOutput['totalFiles'];
Chris@17 688 $this->reporter->totalErrors += $childOutput['totalErrors'];
Chris@17 689 $this->reporter->totalWarnings += $childOutput['totalWarnings'];
Chris@17 690 $this->reporter->totalFixable += $childOutput['totalFixable'];
Chris@17 691 $this->reporter->totalFixed += $childOutput['totalFixed'];
Chris@17 692 }
Chris@17 693
Chris@17 694 if (isset($debugOutput) === true) {
Chris@17 695 echo $debugOutput;
Chris@17 696 }
Chris@17 697
Chris@17 698 if (isset($childCache) === true) {
Chris@17 699 foreach ($childCache as $path => $cache) {
Chris@17 700 Cache::set($path, $cache);
Chris@17 701 }
Chris@17 702 }
Chris@17 703
Chris@17 704 unlink($procData['out']);
Chris@17 705 unset($childProcs[$key]);
Chris@17 706
Chris@17 707 $numProcessed++;
Chris@17 708
Chris@17 709 // Fake a processed file so we can print progress output for the batch.
Chris@17 710 $file = new DummyFile(null, $this->ruleset, $this->config);
Chris@17 711 $file->setErrorCounts(
Chris@17 712 $childOutput['totalErrors'],
Chris@17 713 $childOutput['totalWarnings'],
Chris@17 714 $childOutput['totalFixable'],
Chris@17 715 $childOutput['totalFixed']
Chris@17 716 );
Chris@17 717 $this->printProgress($file, $totalBatches, $numProcessed);
Chris@17 718 }//end if
Chris@17 719 }//end if
Chris@17 720 }//end foreach
Chris@17 721 }//end while
Chris@17 722
Chris@17 723 }//end processChildProcs()
Chris@17 724
Chris@17 725
Chris@17 726 /**
Chris@17 727 * Print progress information for a single processed file.
Chris@17 728 *
Chris@17 729 * @param File $file The file that was processed.
Chris@17 730 * @param int $numFiles The total number of files to process.
Chris@17 731 * @param int $numProcessed The number of files that have been processed,
Chris@17 732 * including this one.
Chris@17 733 *
Chris@17 734 * @return void
Chris@17 735 */
Chris@17 736 public function printProgress($file, $numFiles, $numProcessed)
Chris@17 737 {
Chris@17 738 if (PHP_CODESNIFFER_VERBOSITY > 0
Chris@17 739 || $this->config->showProgress === false
Chris@17 740 ) {
Chris@17 741 return;
Chris@17 742 }
Chris@17 743
Chris@17 744 // Show progress information.
Chris@17 745 if ($file->ignored === true) {
Chris@17 746 echo 'S';
Chris@17 747 } else {
Chris@17 748 $errors = $file->getErrorCount();
Chris@17 749 $warnings = $file->getWarningCount();
Chris@17 750 $fixable = $file->getFixableCount();
Chris@17 751 $fixed = $file->getFixedCount();
Chris@17 752
Chris@17 753 if (PHP_CODESNIFFER_CBF === true) {
Chris@17 754 // Files with fixed errors or warnings are F (green).
Chris@17 755 // Files with unfixable errors or warnings are E (red).
Chris@17 756 // Files with no errors or warnings are . (black).
Chris@17 757 if ($fixable > 0) {
Chris@17 758 if ($this->config->colors === true) {
Chris@17 759 echo "\033[31m";
Chris@17 760 }
Chris@17 761
Chris@17 762 echo 'E';
Chris@17 763
Chris@17 764 if ($this->config->colors === true) {
Chris@17 765 echo "\033[0m";
Chris@17 766 }
Chris@17 767 } else if ($fixed > 0) {
Chris@17 768 if ($this->config->colors === true) {
Chris@17 769 echo "\033[32m";
Chris@17 770 }
Chris@17 771
Chris@17 772 echo 'F';
Chris@17 773
Chris@17 774 if ($this->config->colors === true) {
Chris@17 775 echo "\033[0m";
Chris@17 776 }
Chris@17 777 } else {
Chris@17 778 echo '.';
Chris@17 779 }//end if
Chris@17 780 } else {
Chris@17 781 // Files with errors are E (red).
Chris@17 782 // Files with fixable errors are E (green).
Chris@17 783 // Files with warnings are W (yellow).
Chris@17 784 // Files with fixable warnings are W (green).
Chris@17 785 // Files with no errors or warnings are . (black).
Chris@17 786 if ($errors > 0) {
Chris@17 787 if ($this->config->colors === true) {
Chris@17 788 if ($fixable > 0) {
Chris@17 789 echo "\033[32m";
Chris@17 790 } else {
Chris@17 791 echo "\033[31m";
Chris@17 792 }
Chris@17 793 }
Chris@17 794
Chris@17 795 echo 'E';
Chris@17 796
Chris@17 797 if ($this->config->colors === true) {
Chris@17 798 echo "\033[0m";
Chris@17 799 }
Chris@17 800 } else if ($warnings > 0) {
Chris@17 801 if ($this->config->colors === true) {
Chris@17 802 if ($fixable > 0) {
Chris@17 803 echo "\033[32m";
Chris@17 804 } else {
Chris@17 805 echo "\033[33m";
Chris@17 806 }
Chris@17 807 }
Chris@17 808
Chris@17 809 echo 'W';
Chris@17 810
Chris@17 811 if ($this->config->colors === true) {
Chris@17 812 echo "\033[0m";
Chris@17 813 }
Chris@17 814 } else {
Chris@17 815 echo '.';
Chris@17 816 }//end if
Chris@17 817 }//end if
Chris@17 818 }//end if
Chris@17 819
Chris@17 820 $numPerLine = 60;
Chris@17 821 if ($numProcessed !== $numFiles && ($numProcessed % $numPerLine) !== 0) {
Chris@17 822 return;
Chris@17 823 }
Chris@17 824
Chris@17 825 $percent = round(($numProcessed / $numFiles) * 100);
Chris@17 826 $padding = (strlen($numFiles) - strlen($numProcessed));
Chris@17 827 if ($numProcessed === $numFiles && $numFiles > $numPerLine) {
Chris@17 828 $padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine)));
Chris@17 829 }
Chris@17 830
Chris@17 831 echo str_repeat(' ', $padding)." $numProcessed / $numFiles ($percent%)".PHP_EOL;
Chris@17 832
Chris@17 833 }//end printProgress()
Chris@17 834
Chris@17 835
Chris@17 836 }//end class