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