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

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@17 1 <?php
Chris@17 2 /**
Chris@17 3 * 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@18 233 * Exits if the minimum requirements of PHP_CodeSniffer are not met.
Chris@17 234 *
Chris@17 235 * @return array
Chris@18 236 * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
Chris@17 237 */
Chris@17 238 public function checkRequirements()
Chris@17 239 {
Chris@17 240 // Check the PHP version.
Chris@17 241 if (PHP_VERSION_ID < 50400) {
Chris@17 242 $error = 'ERROR: PHP_CodeSniffer requires PHP version 5.4.0 or greater.'.PHP_EOL;
Chris@17 243 throw new DeepExitException($error, 3);
Chris@17 244 }
Chris@17 245
Chris@18 246 $requiredExtensions = [
Chris@18 247 'tokenizer',
Chris@18 248 'xmlwriter',
Chris@18 249 'SimpleXML',
Chris@18 250 ];
Chris@18 251 $missingExtensions = [];
Chris@18 252
Chris@18 253 foreach ($requiredExtensions as $extension) {
Chris@18 254 if (extension_loaded($extension) === false) {
Chris@18 255 $missingExtensions[] = $extension;
Chris@18 256 }
Chris@18 257 }
Chris@18 258
Chris@18 259 if (empty($missingExtensions) === false) {
Chris@18 260 $last = array_pop($requiredExtensions);
Chris@18 261 $required = implode(', ', $requiredExtensions);
Chris@18 262 $required .= ' and '.$last;
Chris@18 263
Chris@18 264 if (count($missingExtensions) === 1) {
Chris@18 265 $missing = $missingExtensions[0];
Chris@18 266 } else {
Chris@18 267 $last = array_pop($missingExtensions);
Chris@18 268 $missing = implode(', ', $missingExtensions);
Chris@18 269 $missing .= ' and '.$last;
Chris@18 270 }
Chris@18 271
Chris@18 272 $error = 'ERROR: PHP_CodeSniffer requires the %s extensions to be enabled. Please enable %s.'.PHP_EOL;
Chris@18 273 $error = sprintf($error, $required, $missing);
Chris@17 274 throw new DeepExitException($error, 3);
Chris@17 275 }
Chris@17 276
Chris@17 277 }//end checkRequirements()
Chris@17 278
Chris@17 279
Chris@17 280 /**
Chris@17 281 * Init the rulesets and other high-level settings.
Chris@17 282 *
Chris@17 283 * @return void
Chris@18 284 * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
Chris@17 285 */
Chris@17 286 public function init()
Chris@17 287 {
Chris@17 288 if (defined('PHP_CODESNIFFER_CBF') === false) {
Chris@17 289 define('PHP_CODESNIFFER_CBF', false);
Chris@17 290 }
Chris@17 291
Chris@17 292 // Ensure this option is enabled or else line endings will not always
Chris@17 293 // be detected properly for files created on a Mac with the /r line ending.
Chris@17 294 ini_set('auto_detect_line_endings', true);
Chris@17 295
Chris@18 296 // Disable the PCRE JIT as this caused issues with parallel running.
Chris@18 297 ini_set('pcre.jit', false);
Chris@18 298
Chris@17 299 // Check that the standards are valid.
Chris@17 300 foreach ($this->config->standards as $standard) {
Chris@17 301 if (Util\Standards::isInstalledStandard($standard) === false) {
Chris@17 302 // They didn't select a valid coding standard, so help them
Chris@17 303 // out by letting them know which standards are installed.
Chris@17 304 $error = 'ERROR: the "'.$standard.'" coding standard is not installed. ';
Chris@17 305 ob_start();
Chris@17 306 Util\Standards::printInstalledStandards();
Chris@17 307 $error .= ob_get_contents();
Chris@17 308 ob_end_clean();
Chris@17 309 throw new DeepExitException($error, 3);
Chris@17 310 }
Chris@17 311 }
Chris@17 312
Chris@17 313 // Saves passing the Config object into other objects that only need
Chris@18 314 // the verbosity flag for debug output.
Chris@17 315 if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
Chris@17 316 define('PHP_CODESNIFFER_VERBOSITY', $this->config->verbosity);
Chris@17 317 }
Chris@17 318
Chris@17 319 // Create this class so it is autoloaded and sets up a bunch
Chris@17 320 // of PHP_CodeSniffer-specific token type constants.
Chris@17 321 $tokens = new Util\Tokens();
Chris@17 322
Chris@17 323 // Allow autoloading of custom files inside installed standards.
Chris@17 324 $installedStandards = Standards::getInstalledStandardDetails();
Chris@17 325 foreach ($installedStandards as $name => $details) {
Chris@17 326 Autoload::addSearchPath($details['path'], $details['namespace']);
Chris@17 327 }
Chris@17 328
Chris@17 329 // The ruleset contains all the information about how the files
Chris@17 330 // should be checked and/or fixed.
Chris@17 331 try {
Chris@17 332 $this->ruleset = new Ruleset($this->config);
Chris@17 333 } catch (RuntimeException $e) {
Chris@17 334 $error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL;
Chris@17 335 $error .= $this->config->printShortUsage(true);
Chris@17 336 throw new DeepExitException($error, 3);
Chris@17 337 }
Chris@17 338
Chris@17 339 }//end init()
Chris@17 340
Chris@17 341
Chris@17 342 /**
Chris@17 343 * Performs the run.
Chris@17 344 *
Chris@17 345 * @return int The number of errors and warnings found.
Chris@18 346 * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
Chris@18 347 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException
Chris@17 348 */
Chris@17 349 private function run()
Chris@17 350 {
Chris@17 351 // The class that manages all reporters for the run.
Chris@17 352 $this->reporter = new Reporter($this->config);
Chris@17 353
Chris@17 354 // Include bootstrap files.
Chris@17 355 foreach ($this->config->bootstrap as $bootstrap) {
Chris@17 356 include $bootstrap;
Chris@17 357 }
Chris@17 358
Chris@17 359 if ($this->config->stdin === true) {
Chris@17 360 $fileContents = $this->config->stdinContent;
Chris@17 361 if ($fileContents === null) {
Chris@17 362 $handle = fopen('php://stdin', 'r');
Chris@17 363 stream_set_blocking($handle, true);
Chris@17 364 $fileContents = stream_get_contents($handle);
Chris@17 365 fclose($handle);
Chris@17 366 }
Chris@17 367
Chris@17 368 $todo = new FileList($this->config, $this->ruleset);
Chris@17 369 $dummy = new DummyFile($fileContents, $this->ruleset, $this->config);
Chris@17 370 $todo->addFile($dummy->path, $dummy);
Chris@17 371 } else {
Chris@17 372 if (empty($this->config->files) === true) {
Chris@17 373 $error = 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL;
Chris@17 374 $error .= $this->config->printShortUsage(true);
Chris@17 375 throw new DeepExitException($error, 3);
Chris@17 376 }
Chris@17 377
Chris@17 378 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 379 echo 'Creating file list... ';
Chris@17 380 }
Chris@17 381
Chris@17 382 $todo = new FileList($this->config, $this->ruleset);
Chris@17 383
Chris@17 384 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 385 $numFiles = count($todo);
Chris@17 386 echo "DONE ($numFiles files in queue)".PHP_EOL;
Chris@17 387 }
Chris@17 388
Chris@17 389 if ($this->config->cache === true) {
Chris@17 390 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 391 echo 'Loading cache... ';
Chris@17 392 }
Chris@17 393
Chris@17 394 Cache::load($this->ruleset, $this->config);
Chris@17 395
Chris@17 396 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 397 $size = Cache::getSize();
Chris@17 398 echo "DONE ($size files in cache)".PHP_EOL;
Chris@17 399 }
Chris@17 400 }
Chris@17 401 }//end if
Chris@17 402
Chris@17 403 // Turn all sniff errors into exceptions.
Chris@17 404 set_error_handler([$this, 'handleErrors']);
Chris@17 405
Chris@17 406 // If verbosity is too high, turn off parallelism so the
Chris@17 407 // debug output is clean.
Chris@17 408 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 409 $this->config->parallel = 1;
Chris@17 410 }
Chris@17 411
Chris@17 412 // If the PCNTL extension isn't installed, we can't fork.
Chris@17 413 if (function_exists('pcntl_fork') === false) {
Chris@17 414 $this->config->parallel = 1;
Chris@17 415 }
Chris@17 416
Chris@17 417 $lastDir = '';
Chris@17 418 $numFiles = count($todo);
Chris@17 419
Chris@17 420 if ($this->config->parallel === 1) {
Chris@17 421 // Running normally.
Chris@17 422 $numProcessed = 0;
Chris@17 423 foreach ($todo as $path => $file) {
Chris@17 424 if ($file->ignored === false) {
Chris@17 425 $currDir = dirname($path);
Chris@17 426 if ($lastDir !== $currDir) {
Chris@17 427 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 428 echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
Chris@17 429 }
Chris@17 430
Chris@17 431 $lastDir = $currDir;
Chris@17 432 }
Chris@17 433
Chris@17 434 $this->processFile($file);
Chris@17 435 } else if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 436 echo 'Skipping '.basename($file->path).PHP_EOL;
Chris@17 437 }
Chris@17 438
Chris@17 439 $numProcessed++;
Chris@17 440 $this->printProgress($file, $numFiles, $numProcessed);
Chris@17 441 }
Chris@17 442 } else {
Chris@17 443 // Batching and forking.
Chris@17 444 $childProcs = [];
Chris@17 445 $numPerBatch = ceil($numFiles / $this->config->parallel);
Chris@17 446
Chris@17 447 for ($batch = 0; $batch < $this->config->parallel; $batch++) {
Chris@17 448 $startAt = ($batch * $numPerBatch);
Chris@17 449 if ($startAt >= $numFiles) {
Chris@17 450 break;
Chris@17 451 }
Chris@17 452
Chris@17 453 $endAt = ($startAt + $numPerBatch);
Chris@17 454 if ($endAt > $numFiles) {
Chris@17 455 $endAt = $numFiles;
Chris@17 456 }
Chris@17 457
Chris@17 458 $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child');
Chris@17 459 $pid = pcntl_fork();
Chris@17 460 if ($pid === -1) {
Chris@17 461 throw new RuntimeException('Failed to create child process');
Chris@17 462 } else if ($pid !== 0) {
Chris@17 463 $childProcs[] = [
Chris@17 464 'pid' => $pid,
Chris@17 465 'out' => $childOutFilename,
Chris@17 466 ];
Chris@17 467 } else {
Chris@17 468 // Move forward to the start of the batch.
Chris@17 469 $todo->rewind();
Chris@17 470 for ($i = 0; $i < $startAt; $i++) {
Chris@17 471 $todo->next();
Chris@17 472 }
Chris@17 473
Chris@17 474 // Reset the reporter to make sure only figures from this
Chris@17 475 // file batch are recorded.
Chris@17 476 $this->reporter->totalFiles = 0;
Chris@17 477 $this->reporter->totalErrors = 0;
Chris@17 478 $this->reporter->totalWarnings = 0;
Chris@17 479 $this->reporter->totalFixable = 0;
Chris@17 480 $this->reporter->totalFixed = 0;
Chris@17 481
Chris@17 482 // Process the files.
Chris@17 483 $pathsProcessed = [];
Chris@17 484 ob_start();
Chris@17 485 for ($i = $startAt; $i < $endAt; $i++) {
Chris@17 486 $path = $todo->key();
Chris@17 487 $file = $todo->current();
Chris@17 488
Chris@17 489 if ($file->ignored === true) {
Chris@17 490 continue;
Chris@17 491 }
Chris@17 492
Chris@17 493 $currDir = dirname($path);
Chris@17 494 if ($lastDir !== $currDir) {
Chris@17 495 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 496 echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
Chris@17 497 }
Chris@17 498
Chris@17 499 $lastDir = $currDir;
Chris@17 500 }
Chris@17 501
Chris@17 502 $this->processFile($file);
Chris@17 503
Chris@17 504 $pathsProcessed[] = $path;
Chris@17 505 $todo->next();
Chris@17 506 }//end for
Chris@17 507
Chris@17 508 $debugOutput = ob_get_contents();
Chris@17 509 ob_end_clean();
Chris@17 510
Chris@17 511 // Write information about the run to the filesystem
Chris@17 512 // so it can be picked up by the main process.
Chris@17 513 $childOutput = [
Chris@17 514 'totalFiles' => $this->reporter->totalFiles,
Chris@17 515 'totalErrors' => $this->reporter->totalErrors,
Chris@17 516 'totalWarnings' => $this->reporter->totalWarnings,
Chris@17 517 'totalFixable' => $this->reporter->totalFixable,
Chris@17 518 'totalFixed' => $this->reporter->totalFixed,
Chris@17 519 ];
Chris@17 520
Chris@17 521 $output = '<'.'?php'."\n".' $childOutput = ';
Chris@17 522 $output .= var_export($childOutput, true);
Chris@17 523 $output .= ";\n\$debugOutput = ";
Chris@17 524 $output .= var_export($debugOutput, true);
Chris@17 525
Chris@17 526 if ($this->config->cache === true) {
Chris@17 527 $childCache = [];
Chris@17 528 foreach ($pathsProcessed as $path) {
Chris@17 529 $childCache[$path] = Cache::get($path);
Chris@17 530 }
Chris@17 531
Chris@17 532 $output .= ";\n\$childCache = ";
Chris@17 533 $output .= var_export($childCache, true);
Chris@17 534 }
Chris@17 535
Chris@17 536 $output .= ";\n?".'>';
Chris@17 537 file_put_contents($childOutFilename, $output);
Chris@17 538 exit($pid);
Chris@17 539 }//end if
Chris@17 540 }//end for
Chris@17 541
Chris@18 542 $success = $this->processChildProcs($childProcs);
Chris@18 543 if ($success === false) {
Chris@18 544 throw new RuntimeException('One or more child processes failed to run');
Chris@18 545 }
Chris@17 546 }//end if
Chris@17 547
Chris@17 548 restore_error_handler();
Chris@17 549
Chris@17 550 if (PHP_CODESNIFFER_VERBOSITY === 0
Chris@17 551 && $this->config->interactive === false
Chris@17 552 && $this->config->showProgress === true
Chris@17 553 ) {
Chris@17 554 echo PHP_EOL.PHP_EOL;
Chris@17 555 }
Chris@17 556
Chris@17 557 if ($this->config->cache === true) {
Chris@17 558 Cache::save();
Chris@17 559 }
Chris@17 560
Chris@17 561 $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit');
Chris@17 562 $ignoreErrors = Config::getConfigData('ignore_errors_on_exit');
Chris@17 563
Chris@17 564 $return = ($this->reporter->totalErrors + $this->reporter->totalWarnings);
Chris@17 565 if ($ignoreErrors !== null) {
Chris@17 566 $ignoreErrors = (bool) $ignoreErrors;
Chris@17 567 if ($ignoreErrors === true) {
Chris@17 568 $return -= $this->reporter->totalErrors;
Chris@17 569 }
Chris@17 570 }
Chris@17 571
Chris@17 572 if ($ignoreWarnings !== null) {
Chris@17 573 $ignoreWarnings = (bool) $ignoreWarnings;
Chris@17 574 if ($ignoreWarnings === true) {
Chris@17 575 $return -= $this->reporter->totalWarnings;
Chris@17 576 }
Chris@17 577 }
Chris@17 578
Chris@17 579 return $return;
Chris@17 580
Chris@17 581 }//end run()
Chris@17 582
Chris@17 583
Chris@17 584 /**
Chris@17 585 * Converts all PHP errors into exceptions.
Chris@17 586 *
Chris@17 587 * This method forces a sniff to stop processing if it is not
Chris@17 588 * able to handle a specific piece of code, instead of continuing
Chris@17 589 * and potentially getting into a loop.
Chris@17 590 *
Chris@17 591 * @param int $code The level of error raised.
Chris@17 592 * @param string $message The error message.
Chris@17 593 * @param string $file The path of the file that raised the error.
Chris@17 594 * @param int $line The line number the error was raised at.
Chris@17 595 *
Chris@17 596 * @return void
Chris@18 597 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException
Chris@17 598 */
Chris@17 599 public function handleErrors($code, $message, $file, $line)
Chris@17 600 {
Chris@17 601 if ((error_reporting() & $code) === 0) {
Chris@17 602 // This type of error is being muted.
Chris@17 603 return true;
Chris@17 604 }
Chris@17 605
Chris@17 606 throw new RuntimeException("$message in $file on line $line");
Chris@17 607
Chris@17 608 }//end handleErrors()
Chris@17 609
Chris@17 610
Chris@17 611 /**
Chris@17 612 * Processes a single file, including checking and fixing.
Chris@17 613 *
Chris@17 614 * @param \PHP_CodeSniffer\Files\File $file The file to be processed.
Chris@17 615 *
Chris@17 616 * @return void
Chris@18 617 * @throws \PHP_CodeSniffer\Exceptions\DeepExitException
Chris@17 618 */
Chris@17 619 public function processFile($file)
Chris@17 620 {
Chris@17 621 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 622 $startTime = microtime(true);
Chris@17 623 echo 'Processing '.basename($file->path).' ';
Chris@17 624 if (PHP_CODESNIFFER_VERBOSITY > 1) {
Chris@17 625 echo PHP_EOL;
Chris@17 626 }
Chris@17 627 }
Chris@17 628
Chris@17 629 try {
Chris@17 630 $file->process();
Chris@17 631
Chris@17 632 if (PHP_CODESNIFFER_VERBOSITY > 0) {
Chris@17 633 $timeTaken = ((microtime(true) - $startTime) * 1000);
Chris@17 634 if ($timeTaken < 1000) {
Chris@17 635 $timeTaken = round($timeTaken);
Chris@17 636 echo "DONE in {$timeTaken}ms";
Chris@17 637 } else {
Chris@17 638 $timeTaken = round(($timeTaken / 1000), 2);
Chris@17 639 echo "DONE in $timeTaken secs";
Chris@17 640 }
Chris@17 641
Chris@17 642 if (PHP_CODESNIFFER_CBF === true) {
Chris@17 643 $errors = $file->getFixableCount();
Chris@17 644 echo " ($errors fixable violations)".PHP_EOL;
Chris@17 645 } else {
Chris@17 646 $errors = $file->getErrorCount();
Chris@17 647 $warnings = $file->getWarningCount();
Chris@17 648 echo " ($errors errors, $warnings warnings)".PHP_EOL;
Chris@17 649 }
Chris@17 650 }
Chris@17 651 } catch (\Exception $e) {
Chris@17 652 $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
Chris@17 653 $file->addErrorOnLine($error, 1, 'Internal.Exception');
Chris@17 654 }//end try
Chris@17 655
Chris@17 656 $this->reporter->cacheFileReport($file, $this->config);
Chris@17 657
Chris@17 658 if ($this->config->interactive === true) {
Chris@17 659 /*
Chris@17 660 Running interactively.
Chris@17 661 Print the error report for the current file and then wait for user input.
Chris@17 662 */
Chris@17 663
Chris@17 664 // Get current violations and then clear the list to make sure
Chris@17 665 // we only print violations for a single file each time.
Chris@17 666 $numErrors = null;
Chris@17 667 while ($numErrors !== 0) {
Chris@17 668 $numErrors = ($file->getErrorCount() + $file->getWarningCount());
Chris@17 669 if ($numErrors === 0) {
Chris@17 670 continue;
Chris@17 671 }
Chris@17 672
Chris@17 673 $this->reporter->printReport('full');
Chris@17 674
Chris@17 675 echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
Chris@17 676 $input = fgets(STDIN);
Chris@17 677 $input = trim($input);
Chris@17 678
Chris@17 679 switch ($input) {
Chris@17 680 case 's':
Chris@17 681 break(2);
Chris@17 682 case 'q':
Chris@17 683 throw new DeepExitException('', 0);
Chris@17 684 default:
Chris@17 685 // Repopulate the sniffs because some of them save their state
Chris@17 686 // and only clear it when the file changes, but we are rechecking
Chris@17 687 // the same file.
Chris@17 688 $file->ruleset->populateTokenListeners();
Chris@17 689 $file->reloadContent();
Chris@17 690 $file->process();
Chris@17 691 $this->reporter->cacheFileReport($file, $this->config);
Chris@17 692 break;
Chris@17 693 }
Chris@17 694 }//end while
Chris@17 695 }//end if
Chris@17 696
Chris@17 697 // Clean up the file to save (a lot of) memory.
Chris@17 698 $file->cleanUp();
Chris@17 699
Chris@17 700 }//end processFile()
Chris@17 701
Chris@17 702
Chris@17 703 /**
Chris@17 704 * Waits for child processes to complete and cleans up after them.
Chris@17 705 *
Chris@17 706 * The reporting information returned by each child process is merged
Chris@17 707 * into the main reporter class.
Chris@17 708 *
Chris@17 709 * @param array $childProcs An array of child processes to wait for.
Chris@17 710 *
Chris@17 711 * @return void
Chris@17 712 */
Chris@17 713 private function processChildProcs($childProcs)
Chris@17 714 {
Chris@17 715 $numProcessed = 0;
Chris@17 716 $totalBatches = count($childProcs);
Chris@17 717
Chris@18 718 $success = true;
Chris@18 719
Chris@17 720 while (count($childProcs) > 0) {
Chris@17 721 foreach ($childProcs as $key => $procData) {
Chris@17 722 $res = pcntl_waitpid($procData['pid'], $status, WNOHANG);
Chris@17 723 if ($res === $procData['pid']) {
Chris@17 724 if (file_exists($procData['out']) === true) {
Chris@17 725 include $procData['out'];
Chris@18 726
Chris@18 727 unlink($procData['out']);
Chris@18 728 unset($childProcs[$key]);
Chris@18 729
Chris@18 730 $numProcessed++;
Chris@18 731
Chris@18 732 if (isset($childOutput) === false) {
Chris@18 733 // The child process died, so the run has failed.
Chris@18 734 $file = new DummyFile(null, $this->ruleset, $this->config);
Chris@18 735 $file->setErrorCounts(1, 0, 0, 0);
Chris@18 736 $this->printProgress($file, $totalBatches, $numProcessed);
Chris@18 737 $success = false;
Chris@18 738 continue;
Chris@17 739 }
Chris@17 740
Chris@18 741 $this->reporter->totalFiles += $childOutput['totalFiles'];
Chris@18 742 $this->reporter->totalErrors += $childOutput['totalErrors'];
Chris@18 743 $this->reporter->totalWarnings += $childOutput['totalWarnings'];
Chris@18 744 $this->reporter->totalFixable += $childOutput['totalFixable'];
Chris@18 745 $this->reporter->totalFixed += $childOutput['totalFixed'];
Chris@18 746
Chris@17 747 if (isset($debugOutput) === true) {
Chris@17 748 echo $debugOutput;
Chris@17 749 }
Chris@17 750
Chris@17 751 if (isset($childCache) === true) {
Chris@17 752 foreach ($childCache as $path => $cache) {
Chris@17 753 Cache::set($path, $cache);
Chris@17 754 }
Chris@17 755 }
Chris@17 756
Chris@17 757 // Fake a processed file so we can print progress output for the batch.
Chris@17 758 $file = new DummyFile(null, $this->ruleset, $this->config);
Chris@17 759 $file->setErrorCounts(
Chris@17 760 $childOutput['totalErrors'],
Chris@17 761 $childOutput['totalWarnings'],
Chris@17 762 $childOutput['totalFixable'],
Chris@17 763 $childOutput['totalFixed']
Chris@17 764 );
Chris@17 765 $this->printProgress($file, $totalBatches, $numProcessed);
Chris@17 766 }//end if
Chris@17 767 }//end if
Chris@17 768 }//end foreach
Chris@17 769 }//end while
Chris@17 770
Chris@18 771 return $success;
Chris@18 772
Chris@17 773 }//end processChildProcs()
Chris@17 774
Chris@17 775
Chris@17 776 /**
Chris@17 777 * Print progress information for a single processed file.
Chris@17 778 *
Chris@18 779 * @param \PHP_CodeSniffer\Files\File $file The file that was processed.
Chris@18 780 * @param int $numFiles The total number of files to process.
Chris@18 781 * @param int $numProcessed The number of files that have been processed,
Chris@18 782 * including this one.
Chris@17 783 *
Chris@17 784 * @return void
Chris@17 785 */
Chris@18 786 public function printProgress(File $file, $numFiles, $numProcessed)
Chris@17 787 {
Chris@17 788 if (PHP_CODESNIFFER_VERBOSITY > 0
Chris@17 789 || $this->config->showProgress === false
Chris@17 790 ) {
Chris@17 791 return;
Chris@17 792 }
Chris@17 793
Chris@17 794 // Show progress information.
Chris@17 795 if ($file->ignored === true) {
Chris@17 796 echo 'S';
Chris@17 797 } else {
Chris@17 798 $errors = $file->getErrorCount();
Chris@17 799 $warnings = $file->getWarningCount();
Chris@17 800 $fixable = $file->getFixableCount();
Chris@17 801 $fixed = $file->getFixedCount();
Chris@17 802
Chris@17 803 if (PHP_CODESNIFFER_CBF === true) {
Chris@17 804 // Files with fixed errors or warnings are F (green).
Chris@17 805 // Files with unfixable errors or warnings are E (red).
Chris@17 806 // Files with no errors or warnings are . (black).
Chris@17 807 if ($fixable > 0) {
Chris@17 808 if ($this->config->colors === true) {
Chris@17 809 echo "\033[31m";
Chris@17 810 }
Chris@17 811
Chris@17 812 echo 'E';
Chris@17 813
Chris@17 814 if ($this->config->colors === true) {
Chris@17 815 echo "\033[0m";
Chris@17 816 }
Chris@17 817 } else if ($fixed > 0) {
Chris@17 818 if ($this->config->colors === true) {
Chris@17 819 echo "\033[32m";
Chris@17 820 }
Chris@17 821
Chris@17 822 echo 'F';
Chris@17 823
Chris@17 824 if ($this->config->colors === true) {
Chris@17 825 echo "\033[0m";
Chris@17 826 }
Chris@17 827 } else {
Chris@17 828 echo '.';
Chris@17 829 }//end if
Chris@17 830 } else {
Chris@17 831 // Files with errors are E (red).
Chris@17 832 // Files with fixable errors are E (green).
Chris@17 833 // Files with warnings are W (yellow).
Chris@17 834 // Files with fixable warnings are W (green).
Chris@17 835 // Files with no errors or warnings are . (black).
Chris@17 836 if ($errors > 0) {
Chris@17 837 if ($this->config->colors === true) {
Chris@17 838 if ($fixable > 0) {
Chris@17 839 echo "\033[32m";
Chris@17 840 } else {
Chris@17 841 echo "\033[31m";
Chris@17 842 }
Chris@17 843 }
Chris@17 844
Chris@17 845 echo 'E';
Chris@17 846
Chris@17 847 if ($this->config->colors === true) {
Chris@17 848 echo "\033[0m";
Chris@17 849 }
Chris@17 850 } else if ($warnings > 0) {
Chris@17 851 if ($this->config->colors === true) {
Chris@17 852 if ($fixable > 0) {
Chris@17 853 echo "\033[32m";
Chris@17 854 } else {
Chris@17 855 echo "\033[33m";
Chris@17 856 }
Chris@17 857 }
Chris@17 858
Chris@17 859 echo 'W';
Chris@17 860
Chris@17 861 if ($this->config->colors === true) {
Chris@17 862 echo "\033[0m";
Chris@17 863 }
Chris@17 864 } else {
Chris@17 865 echo '.';
Chris@17 866 }//end if
Chris@17 867 }//end if
Chris@17 868 }//end if
Chris@17 869
Chris@17 870 $numPerLine = 60;
Chris@17 871 if ($numProcessed !== $numFiles && ($numProcessed % $numPerLine) !== 0) {
Chris@17 872 return;
Chris@17 873 }
Chris@17 874
Chris@17 875 $percent = round(($numProcessed / $numFiles) * 100);
Chris@17 876 $padding = (strlen($numFiles) - strlen($numProcessed));
Chris@17 877 if ($numProcessed === $numFiles && $numFiles > $numPerLine) {
Chris@17 878 $padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine)));
Chris@17 879 }
Chris@17 880
Chris@17 881 echo str_repeat(' ', $padding)." $numProcessed / $numFiles ($percent%)".PHP_EOL;
Chris@17 882
Chris@17 883 }//end printProgress()
Chris@17 884
Chris@17 885
Chris@17 886 }//end class