Chris@4: Chris@4: * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) Chris@4: * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence Chris@4: */ Chris@4: Chris@4: namespace PHP_CodeSniffer; Chris@4: Chris@4: use PHP_CodeSniffer\Files\FileList; Chris@4: use PHP_CodeSniffer\Files\File; Chris@4: use PHP_CodeSniffer\Files\DummyFile; Chris@4: use PHP_CodeSniffer\Util\Cache; Chris@4: use PHP_CodeSniffer\Util\Common; Chris@4: use PHP_CodeSniffer\Util\Standards; Chris@4: use PHP_CodeSniffer\Exceptions\RuntimeException; Chris@4: use PHP_CodeSniffer\Exceptions\DeepExitException; Chris@4: Chris@4: class Runner Chris@4: { Chris@4: Chris@4: /** Chris@4: * The config data for the run. Chris@4: * Chris@4: * @var \PHP_CodeSniffer\Config Chris@4: */ Chris@4: public $config = null; Chris@4: Chris@4: /** Chris@4: * The ruleset used for the run. Chris@4: * Chris@4: * @var \PHP_CodeSniffer\Ruleset Chris@4: */ Chris@4: public $ruleset = null; Chris@4: Chris@4: /** Chris@4: * The reporter used for generating reports after the run. Chris@4: * Chris@4: * @var \PHP_CodeSniffer\Reporter Chris@4: */ Chris@4: public $reporter = null; Chris@4: Chris@4: Chris@4: /** Chris@4: * Run the PHPCS script. Chris@4: * Chris@4: * @return array Chris@4: */ Chris@4: public function runPHPCS() Chris@4: { Chris@4: try { Chris@4: Util\Timing::startTiming(); Chris@4: Runner::checkRequirements(); Chris@4: Chris@4: if (defined('PHP_CODESNIFFER_CBF') === false) { Chris@4: define('PHP_CODESNIFFER_CBF', false); Chris@4: } Chris@4: Chris@4: // Creating the Config object populates it with all required settings Chris@4: // based on the CLI arguments provided to the script and any config Chris@4: // values the user has set. Chris@4: $this->config = new Config(); Chris@4: Chris@4: // Init the run and load the rulesets to set additional config vars. Chris@4: $this->init(); Chris@4: Chris@4: // Print a list of sniffs in each of the supplied standards. Chris@4: // We fudge the config here so that each standard is explained in isolation. Chris@4: if ($this->config->explain === true) { Chris@4: $standards = $this->config->standards; Chris@4: foreach ($standards as $standard) { Chris@4: $this->config->standards = [$standard]; Chris@4: $ruleset = new Ruleset($this->config); Chris@4: $ruleset->explain(); Chris@4: } Chris@4: Chris@4: return 0; Chris@4: } Chris@4: Chris@4: // Generate documentation for each of the supplied standards. Chris@4: if ($this->config->generator !== null) { Chris@4: $standards = $this->config->standards; Chris@4: foreach ($standards as $standard) { Chris@4: $this->config->standards = [$standard]; Chris@4: $ruleset = new Ruleset($this->config); Chris@4: $class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator; Chris@4: $generator = new $class($ruleset); Chris@4: $generator->generate(); Chris@4: } Chris@4: Chris@4: return 0; Chris@4: } Chris@4: Chris@4: // Other report formats don't really make sense in interactive mode Chris@4: // so we hard-code the full report here and when outputting. Chris@4: // We also ensure parallel processing is off because we need to do one file at a time. Chris@4: if ($this->config->interactive === true) { Chris@4: $this->config->reports = ['full' => null]; Chris@4: $this->config->parallel = 1; Chris@4: $this->config->showProgress = false; Chris@4: } Chris@4: Chris@4: // Disable caching if we are processing STDIN as we can't be 100% Chris@4: // sure where the file came from or if it will change in the future. Chris@4: if ($this->config->stdin === true) { Chris@4: $this->config->cache = false; Chris@4: } Chris@4: Chris@4: $numErrors = $this->run(); Chris@4: Chris@4: // Print all the reports for this run. Chris@4: $toScreen = $this->reporter->printReports(); Chris@4: Chris@4: // Only print timer output if no reports were Chris@4: // printed to the screen so we don't put additional output Chris@4: // in something like an XML report. If we are printing to screen, Chris@4: // the report types would have already worked out who should Chris@4: // print the timer info. Chris@4: if ($this->config->interactive === false Chris@4: && ($toScreen === false Chris@4: || (($this->reporter->totalErrors + $this->reporter->totalWarnings) === 0 && $this->config->showProgress === true)) Chris@4: ) { Chris@4: Util\Timing::printRunTime(); Chris@4: } Chris@4: } catch (DeepExitException $e) { Chris@4: echo $e->getMessage(); Chris@4: return $e->getCode(); Chris@4: }//end try Chris@4: Chris@4: if ($numErrors === 0) { Chris@4: // No errors found. Chris@4: return 0; Chris@4: } else if ($this->reporter->totalFixable === 0) { Chris@4: // Errors found, but none of them can be fixed by PHPCBF. Chris@4: return 1; Chris@4: } else { Chris@4: // Errors found, and some can be fixed by PHPCBF. Chris@4: return 2; Chris@4: } Chris@4: Chris@4: }//end runPHPCS() Chris@4: Chris@4: Chris@4: /** Chris@4: * Run the PHPCBF script. Chris@4: * Chris@4: * @return array Chris@4: */ Chris@4: public function runPHPCBF() Chris@4: { Chris@4: if (defined('PHP_CODESNIFFER_CBF') === false) { Chris@4: define('PHP_CODESNIFFER_CBF', true); Chris@4: } Chris@4: Chris@4: try { Chris@4: Util\Timing::startTiming(); Chris@4: Runner::checkRequirements(); Chris@4: Chris@4: // Creating the Config object populates it with all required settings Chris@4: // based on the CLI arguments provided to the script and any config Chris@4: // values the user has set. Chris@4: $this->config = new Config(); Chris@4: Chris@4: // When processing STDIN, we can't output anything to the screen Chris@4: // or it will end up mixed in with the file output. Chris@4: if ($this->config->stdin === true) { Chris@4: $this->config->verbosity = 0; Chris@4: } Chris@4: Chris@4: // Init the run and load the rulesets to set additional config vars. Chris@4: $this->init(); Chris@4: Chris@4: // When processing STDIN, we only process one file at a time and Chris@4: // we don't process all the way through, so we can't use the parallel Chris@4: // running system. Chris@4: if ($this->config->stdin === true) { Chris@4: $this->config->parallel = 1; Chris@4: } Chris@4: Chris@4: // Override some of the command line settings that might break the fixes. Chris@4: $this->config->generator = null; Chris@4: $this->config->explain = false; Chris@4: $this->config->interactive = false; Chris@4: $this->config->cache = false; Chris@4: $this->config->showSources = false; Chris@4: $this->config->recordErrors = false; Chris@4: $this->config->reportFile = null; Chris@4: $this->config->reports = ['cbf' => null]; Chris@4: Chris@4: // If a standard tries to set command line arguments itself, some Chris@4: // may be blocked because PHPCBF is running, so stop the script Chris@4: // dying if any are found. Chris@4: $this->config->dieOnUnknownArg = false; Chris@4: Chris@4: $this->run(); Chris@4: $this->reporter->printReports(); Chris@4: Chris@4: echo PHP_EOL; Chris@4: Util\Timing::printRunTime(); Chris@4: } catch (DeepExitException $e) { Chris@4: echo $e->getMessage(); Chris@4: return $e->getCode(); Chris@4: }//end try Chris@4: Chris@4: if ($this->reporter->totalFixed === 0) { Chris@4: // Nothing was fixed by PHPCBF. Chris@4: if ($this->reporter->totalFixable === 0) { Chris@4: // Nothing found that could be fixed. Chris@4: return 0; Chris@4: } else { Chris@4: // Something failed to fix. Chris@4: return 2; Chris@4: } Chris@4: } Chris@4: Chris@4: if ($this->reporter->totalFixable === 0) { Chris@4: // PHPCBF fixed all fixable errors. Chris@4: return 1; Chris@4: } Chris@4: Chris@4: // PHPCBF fixed some fixable errors, but others failed to fix. Chris@4: return 2; Chris@4: Chris@4: }//end runPHPCBF() Chris@4: Chris@4: Chris@4: /** Chris@5: * Exits if the minimum requirements of PHP_CodeSniffer are not met. Chris@4: * Chris@4: * @return array Chris@5: * @throws \PHP_CodeSniffer\Exceptions\DeepExitException Chris@4: */ Chris@4: public function checkRequirements() Chris@4: { Chris@4: // Check the PHP version. Chris@4: if (PHP_VERSION_ID < 50400) { Chris@4: $error = 'ERROR: PHP_CodeSniffer requires PHP version 5.4.0 or greater.'.PHP_EOL; Chris@4: throw new DeepExitException($error, 3); Chris@4: } Chris@4: Chris@5: $requiredExtensions = [ Chris@5: 'tokenizer', Chris@5: 'xmlwriter', Chris@5: 'SimpleXML', Chris@5: ]; Chris@5: $missingExtensions = []; Chris@5: Chris@5: foreach ($requiredExtensions as $extension) { Chris@5: if (extension_loaded($extension) === false) { Chris@5: $missingExtensions[] = $extension; Chris@5: } Chris@5: } Chris@5: Chris@5: if (empty($missingExtensions) === false) { Chris@5: $last = array_pop($requiredExtensions); Chris@5: $required = implode(', ', $requiredExtensions); Chris@5: $required .= ' and '.$last; Chris@5: Chris@5: if (count($missingExtensions) === 1) { Chris@5: $missing = $missingExtensions[0]; Chris@5: } else { Chris@5: $last = array_pop($missingExtensions); Chris@5: $missing = implode(', ', $missingExtensions); Chris@5: $missing .= ' and '.$last; Chris@5: } Chris@5: Chris@5: $error = 'ERROR: PHP_CodeSniffer requires the %s extensions to be enabled. Please enable %s.'.PHP_EOL; Chris@5: $error = sprintf($error, $required, $missing); Chris@4: throw new DeepExitException($error, 3); Chris@4: } Chris@4: Chris@4: }//end checkRequirements() Chris@4: Chris@4: Chris@4: /** Chris@4: * Init the rulesets and other high-level settings. Chris@4: * Chris@4: * @return void Chris@5: * @throws \PHP_CodeSniffer\Exceptions\DeepExitException Chris@4: */ Chris@4: public function init() Chris@4: { Chris@4: if (defined('PHP_CODESNIFFER_CBF') === false) { Chris@4: define('PHP_CODESNIFFER_CBF', false); Chris@4: } Chris@4: Chris@4: // Ensure this option is enabled or else line endings will not always Chris@4: // be detected properly for files created on a Mac with the /r line ending. Chris@4: ini_set('auto_detect_line_endings', true); Chris@4: Chris@5: // Disable the PCRE JIT as this caused issues with parallel running. Chris@5: ini_set('pcre.jit', false); Chris@5: Chris@4: // Check that the standards are valid. Chris@4: foreach ($this->config->standards as $standard) { Chris@4: if (Util\Standards::isInstalledStandard($standard) === false) { Chris@4: // They didn't select a valid coding standard, so help them Chris@4: // out by letting them know which standards are installed. Chris@4: $error = 'ERROR: the "'.$standard.'" coding standard is not installed. '; Chris@4: ob_start(); Chris@4: Util\Standards::printInstalledStandards(); Chris@4: $error .= ob_get_contents(); Chris@4: ob_end_clean(); Chris@4: throw new DeepExitException($error, 3); Chris@4: } Chris@4: } Chris@4: Chris@4: // Saves passing the Config object into other objects that only need Chris@5: // the verbosity flag for debug output. Chris@4: if (defined('PHP_CODESNIFFER_VERBOSITY') === false) { Chris@4: define('PHP_CODESNIFFER_VERBOSITY', $this->config->verbosity); Chris@4: } Chris@4: Chris@4: // Create this class so it is autoloaded and sets up a bunch Chris@4: // of PHP_CodeSniffer-specific token type constants. Chris@4: $tokens = new Util\Tokens(); Chris@4: Chris@4: // Allow autoloading of custom files inside installed standards. Chris@4: $installedStandards = Standards::getInstalledStandardDetails(); Chris@4: foreach ($installedStandards as $name => $details) { Chris@4: Autoload::addSearchPath($details['path'], $details['namespace']); Chris@4: } Chris@4: Chris@4: // The ruleset contains all the information about how the files Chris@4: // should be checked and/or fixed. Chris@4: try { Chris@4: $this->ruleset = new Ruleset($this->config); Chris@4: } catch (RuntimeException $e) { Chris@4: $error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL; Chris@4: $error .= $this->config->printShortUsage(true); Chris@4: throw new DeepExitException($error, 3); Chris@4: } Chris@4: Chris@4: }//end init() Chris@4: Chris@4: Chris@4: /** Chris@4: * Performs the run. Chris@4: * Chris@4: * @return int The number of errors and warnings found. Chris@5: * @throws \PHP_CodeSniffer\Exceptions\DeepExitException Chris@5: * @throws \PHP_CodeSniffer\Exceptions\RuntimeException Chris@4: */ Chris@4: private function run() Chris@4: { Chris@4: // The class that manages all reporters for the run. Chris@4: $this->reporter = new Reporter($this->config); Chris@4: Chris@4: // Include bootstrap files. Chris@4: foreach ($this->config->bootstrap as $bootstrap) { Chris@4: include $bootstrap; Chris@4: } Chris@4: Chris@4: if ($this->config->stdin === true) { Chris@4: $fileContents = $this->config->stdinContent; Chris@4: if ($fileContents === null) { Chris@4: $handle = fopen('php://stdin', 'r'); Chris@4: stream_set_blocking($handle, true); Chris@4: $fileContents = stream_get_contents($handle); Chris@4: fclose($handle); Chris@4: } Chris@4: Chris@4: $todo = new FileList($this->config, $this->ruleset); Chris@4: $dummy = new DummyFile($fileContents, $this->ruleset, $this->config); Chris@4: $todo->addFile($dummy->path, $dummy); Chris@4: } else { Chris@4: if (empty($this->config->files) === true) { Chris@4: $error = 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL; Chris@4: $error .= $this->config->printShortUsage(true); Chris@4: throw new DeepExitException($error, 3); Chris@4: } Chris@4: Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: echo 'Creating file list... '; Chris@4: } Chris@4: Chris@4: $todo = new FileList($this->config, $this->ruleset); Chris@4: Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: $numFiles = count($todo); Chris@4: echo "DONE ($numFiles files in queue)".PHP_EOL; Chris@4: } Chris@4: Chris@4: if ($this->config->cache === true) { Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: echo 'Loading cache... '; Chris@4: } Chris@4: Chris@4: Cache::load($this->ruleset, $this->config); Chris@4: Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: $size = Cache::getSize(); Chris@4: echo "DONE ($size files in cache)".PHP_EOL; Chris@4: } Chris@4: } Chris@4: }//end if Chris@4: Chris@4: // Turn all sniff errors into exceptions. Chris@4: set_error_handler([$this, 'handleErrors']); Chris@4: Chris@4: // If verbosity is too high, turn off parallelism so the Chris@4: // debug output is clean. Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@4: $this->config->parallel = 1; Chris@4: } Chris@4: Chris@4: // If the PCNTL extension isn't installed, we can't fork. Chris@4: if (function_exists('pcntl_fork') === false) { Chris@4: $this->config->parallel = 1; Chris@4: } Chris@4: Chris@4: $lastDir = ''; Chris@4: $numFiles = count($todo); Chris@4: Chris@4: if ($this->config->parallel === 1) { Chris@4: // Running normally. Chris@4: $numProcessed = 0; Chris@4: foreach ($todo as $path => $file) { Chris@4: if ($file->ignored === false) { Chris@4: $currDir = dirname($path); Chris@4: if ($lastDir !== $currDir) { Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL; Chris@4: } Chris@4: Chris@4: $lastDir = $currDir; Chris@4: } Chris@4: Chris@4: $this->processFile($file); Chris@4: } else if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: echo 'Skipping '.basename($file->path).PHP_EOL; Chris@4: } Chris@4: Chris@4: $numProcessed++; Chris@4: $this->printProgress($file, $numFiles, $numProcessed); Chris@4: } Chris@4: } else { Chris@4: // Batching and forking. Chris@4: $childProcs = []; Chris@4: $numPerBatch = ceil($numFiles / $this->config->parallel); Chris@4: Chris@4: for ($batch = 0; $batch < $this->config->parallel; $batch++) { Chris@4: $startAt = ($batch * $numPerBatch); Chris@4: if ($startAt >= $numFiles) { Chris@4: break; Chris@4: } Chris@4: Chris@4: $endAt = ($startAt + $numPerBatch); Chris@4: if ($endAt > $numFiles) { Chris@4: $endAt = $numFiles; Chris@4: } Chris@4: Chris@4: $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child'); Chris@4: $pid = pcntl_fork(); Chris@4: if ($pid === -1) { Chris@4: throw new RuntimeException('Failed to create child process'); Chris@4: } else if ($pid !== 0) { Chris@4: $childProcs[] = [ Chris@4: 'pid' => $pid, Chris@4: 'out' => $childOutFilename, Chris@4: ]; Chris@4: } else { Chris@4: // Move forward to the start of the batch. Chris@4: $todo->rewind(); Chris@4: for ($i = 0; $i < $startAt; $i++) { Chris@4: $todo->next(); Chris@4: } Chris@4: Chris@4: // Reset the reporter to make sure only figures from this Chris@4: // file batch are recorded. Chris@4: $this->reporter->totalFiles = 0; Chris@4: $this->reporter->totalErrors = 0; Chris@4: $this->reporter->totalWarnings = 0; Chris@4: $this->reporter->totalFixable = 0; Chris@4: $this->reporter->totalFixed = 0; Chris@4: Chris@4: // Process the files. Chris@4: $pathsProcessed = []; Chris@4: ob_start(); Chris@4: for ($i = $startAt; $i < $endAt; $i++) { Chris@4: $path = $todo->key(); Chris@4: $file = $todo->current(); Chris@4: Chris@4: if ($file->ignored === true) { Chris@4: continue; Chris@4: } Chris@4: Chris@4: $currDir = dirname($path); Chris@4: if ($lastDir !== $currDir) { Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL; Chris@4: } Chris@4: Chris@4: $lastDir = $currDir; Chris@4: } Chris@4: Chris@4: $this->processFile($file); Chris@4: Chris@4: $pathsProcessed[] = $path; Chris@4: $todo->next(); Chris@4: }//end for Chris@4: Chris@4: $debugOutput = ob_get_contents(); Chris@4: ob_end_clean(); Chris@4: Chris@4: // Write information about the run to the filesystem Chris@4: // so it can be picked up by the main process. Chris@4: $childOutput = [ Chris@4: 'totalFiles' => $this->reporter->totalFiles, Chris@4: 'totalErrors' => $this->reporter->totalErrors, Chris@4: 'totalWarnings' => $this->reporter->totalWarnings, Chris@4: 'totalFixable' => $this->reporter->totalFixable, Chris@4: 'totalFixed' => $this->reporter->totalFixed, Chris@4: ]; Chris@4: Chris@4: $output = '<'.'?php'."\n".' $childOutput = '; Chris@4: $output .= var_export($childOutput, true); Chris@4: $output .= ";\n\$debugOutput = "; Chris@4: $output .= var_export($debugOutput, true); Chris@4: Chris@4: if ($this->config->cache === true) { Chris@4: $childCache = []; Chris@4: foreach ($pathsProcessed as $path) { Chris@4: $childCache[$path] = Cache::get($path); Chris@4: } Chris@4: Chris@4: $output .= ";\n\$childCache = "; Chris@4: $output .= var_export($childCache, true); Chris@4: } Chris@4: Chris@4: $output .= ";\n?".'>'; Chris@4: file_put_contents($childOutFilename, $output); Chris@4: exit($pid); Chris@4: }//end if Chris@4: }//end for Chris@4: Chris@5: $success = $this->processChildProcs($childProcs); Chris@5: if ($success === false) { Chris@5: throw new RuntimeException('One or more child processes failed to run'); Chris@5: } Chris@4: }//end if Chris@4: Chris@4: restore_error_handler(); Chris@4: Chris@4: if (PHP_CODESNIFFER_VERBOSITY === 0 Chris@4: && $this->config->interactive === false Chris@4: && $this->config->showProgress === true Chris@4: ) { Chris@4: echo PHP_EOL.PHP_EOL; Chris@4: } Chris@4: Chris@4: if ($this->config->cache === true) { Chris@4: Cache::save(); Chris@4: } Chris@4: Chris@4: $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit'); Chris@4: $ignoreErrors = Config::getConfigData('ignore_errors_on_exit'); Chris@4: Chris@4: $return = ($this->reporter->totalErrors + $this->reporter->totalWarnings); Chris@4: if ($ignoreErrors !== null) { Chris@4: $ignoreErrors = (bool) $ignoreErrors; Chris@4: if ($ignoreErrors === true) { Chris@4: $return -= $this->reporter->totalErrors; Chris@4: } Chris@4: } Chris@4: Chris@4: if ($ignoreWarnings !== null) { Chris@4: $ignoreWarnings = (bool) $ignoreWarnings; Chris@4: if ($ignoreWarnings === true) { Chris@4: $return -= $this->reporter->totalWarnings; Chris@4: } Chris@4: } Chris@4: Chris@4: return $return; Chris@4: Chris@4: }//end run() Chris@4: Chris@4: Chris@4: /** Chris@4: * Converts all PHP errors into exceptions. Chris@4: * Chris@4: * This method forces a sniff to stop processing if it is not Chris@4: * able to handle a specific piece of code, instead of continuing Chris@4: * and potentially getting into a loop. Chris@4: * Chris@4: * @param int $code The level of error raised. Chris@4: * @param string $message The error message. Chris@4: * @param string $file The path of the file that raised the error. Chris@4: * @param int $line The line number the error was raised at. Chris@4: * Chris@4: * @return void Chris@5: * @throws \PHP_CodeSniffer\Exceptions\RuntimeException Chris@4: */ Chris@4: public function handleErrors($code, $message, $file, $line) Chris@4: { Chris@4: if ((error_reporting() & $code) === 0) { Chris@4: // This type of error is being muted. Chris@4: return true; Chris@4: } Chris@4: Chris@4: throw new RuntimeException("$message in $file on line $line"); Chris@4: Chris@4: }//end handleErrors() Chris@4: Chris@4: Chris@4: /** Chris@4: * Processes a single file, including checking and fixing. Chris@4: * Chris@4: * @param \PHP_CodeSniffer\Files\File $file The file to be processed. Chris@4: * Chris@4: * @return void Chris@5: * @throws \PHP_CodeSniffer\Exceptions\DeepExitException Chris@4: */ Chris@4: public function processFile($file) Chris@4: { Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: $startTime = microtime(true); Chris@4: echo 'Processing '.basename($file->path).' '; Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 1) { Chris@4: echo PHP_EOL; Chris@4: } Chris@4: } Chris@4: Chris@4: try { Chris@4: $file->process(); Chris@4: Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0) { Chris@4: $timeTaken = ((microtime(true) - $startTime) * 1000); Chris@4: if ($timeTaken < 1000) { Chris@4: $timeTaken = round($timeTaken); Chris@4: echo "DONE in {$timeTaken}ms"; Chris@4: } else { Chris@4: $timeTaken = round(($timeTaken / 1000), 2); Chris@4: echo "DONE in $timeTaken secs"; Chris@4: } Chris@4: Chris@4: if (PHP_CODESNIFFER_CBF === true) { Chris@4: $errors = $file->getFixableCount(); Chris@4: echo " ($errors fixable violations)".PHP_EOL; Chris@4: } else { Chris@4: $errors = $file->getErrorCount(); Chris@4: $warnings = $file->getWarningCount(); Chris@4: echo " ($errors errors, $warnings warnings)".PHP_EOL; Chris@4: } Chris@4: } Chris@4: } catch (\Exception $e) { Chris@4: $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage(); Chris@4: $file->addErrorOnLine($error, 1, 'Internal.Exception'); Chris@4: }//end try Chris@4: Chris@4: $this->reporter->cacheFileReport($file, $this->config); Chris@4: Chris@4: if ($this->config->interactive === true) { Chris@4: /* Chris@4: Running interactively. Chris@4: Print the error report for the current file and then wait for user input. Chris@4: */ Chris@4: Chris@4: // Get current violations and then clear the list to make sure Chris@4: // we only print violations for a single file each time. Chris@4: $numErrors = null; Chris@4: while ($numErrors !== 0) { Chris@4: $numErrors = ($file->getErrorCount() + $file->getWarningCount()); Chris@4: if ($numErrors === 0) { Chris@4: continue; Chris@4: } Chris@4: Chris@4: $this->reporter->printReport('full'); Chris@4: Chris@4: echo ' to recheck, [s] to skip or [q] to quit : '; Chris@4: $input = fgets(STDIN); Chris@4: $input = trim($input); Chris@4: Chris@4: switch ($input) { Chris@4: case 's': Chris@4: break(2); Chris@4: case 'q': Chris@4: throw new DeepExitException('', 0); Chris@4: default: Chris@4: // Repopulate the sniffs because some of them save their state Chris@4: // and only clear it when the file changes, but we are rechecking Chris@4: // the same file. Chris@4: $file->ruleset->populateTokenListeners(); Chris@4: $file->reloadContent(); Chris@4: $file->process(); Chris@4: $this->reporter->cacheFileReport($file, $this->config); Chris@4: break; Chris@4: } Chris@4: }//end while Chris@4: }//end if Chris@4: Chris@4: // Clean up the file to save (a lot of) memory. Chris@4: $file->cleanUp(); Chris@4: Chris@4: }//end processFile() Chris@4: Chris@4: Chris@4: /** Chris@4: * Waits for child processes to complete and cleans up after them. Chris@4: * Chris@4: * The reporting information returned by each child process is merged Chris@4: * into the main reporter class. Chris@4: * Chris@4: * @param array $childProcs An array of child processes to wait for. Chris@4: * Chris@4: * @return void Chris@4: */ Chris@4: private function processChildProcs($childProcs) Chris@4: { Chris@4: $numProcessed = 0; Chris@4: $totalBatches = count($childProcs); Chris@4: Chris@5: $success = true; Chris@5: Chris@4: while (count($childProcs) > 0) { Chris@4: foreach ($childProcs as $key => $procData) { Chris@4: $res = pcntl_waitpid($procData['pid'], $status, WNOHANG); Chris@4: if ($res === $procData['pid']) { Chris@4: if (file_exists($procData['out']) === true) { Chris@4: include $procData['out']; Chris@5: Chris@5: unlink($procData['out']); Chris@5: unset($childProcs[$key]); Chris@5: Chris@5: $numProcessed++; Chris@5: Chris@5: if (isset($childOutput) === false) { Chris@5: // The child process died, so the run has failed. Chris@5: $file = new DummyFile(null, $this->ruleset, $this->config); Chris@5: $file->setErrorCounts(1, 0, 0, 0); Chris@5: $this->printProgress($file, $totalBatches, $numProcessed); Chris@5: $success = false; Chris@5: continue; Chris@4: } Chris@4: Chris@5: $this->reporter->totalFiles += $childOutput['totalFiles']; Chris@5: $this->reporter->totalErrors += $childOutput['totalErrors']; Chris@5: $this->reporter->totalWarnings += $childOutput['totalWarnings']; Chris@5: $this->reporter->totalFixable += $childOutput['totalFixable']; Chris@5: $this->reporter->totalFixed += $childOutput['totalFixed']; Chris@5: Chris@4: if (isset($debugOutput) === true) { Chris@4: echo $debugOutput; Chris@4: } Chris@4: Chris@4: if (isset($childCache) === true) { Chris@4: foreach ($childCache as $path => $cache) { Chris@4: Cache::set($path, $cache); Chris@4: } Chris@4: } Chris@4: Chris@4: // Fake a processed file so we can print progress output for the batch. Chris@4: $file = new DummyFile(null, $this->ruleset, $this->config); Chris@4: $file->setErrorCounts( Chris@4: $childOutput['totalErrors'], Chris@4: $childOutput['totalWarnings'], Chris@4: $childOutput['totalFixable'], Chris@4: $childOutput['totalFixed'] Chris@4: ); Chris@4: $this->printProgress($file, $totalBatches, $numProcessed); Chris@4: }//end if Chris@4: }//end if Chris@4: }//end foreach Chris@4: }//end while Chris@4: Chris@5: return $success; Chris@5: Chris@4: }//end processChildProcs() Chris@4: Chris@4: Chris@4: /** Chris@4: * Print progress information for a single processed file. Chris@4: * Chris@5: * @param \PHP_CodeSniffer\Files\File $file The file that was processed. Chris@5: * @param int $numFiles The total number of files to process. Chris@5: * @param int $numProcessed The number of files that have been processed, Chris@5: * including this one. Chris@4: * Chris@4: * @return void Chris@4: */ Chris@5: public function printProgress(File $file, $numFiles, $numProcessed) Chris@4: { Chris@4: if (PHP_CODESNIFFER_VERBOSITY > 0 Chris@4: || $this->config->showProgress === false Chris@4: ) { Chris@4: return; Chris@4: } Chris@4: Chris@4: // Show progress information. Chris@4: if ($file->ignored === true) { Chris@4: echo 'S'; Chris@4: } else { Chris@4: $errors = $file->getErrorCount(); Chris@4: $warnings = $file->getWarningCount(); Chris@4: $fixable = $file->getFixableCount(); Chris@4: $fixed = $file->getFixedCount(); Chris@4: Chris@4: if (PHP_CODESNIFFER_CBF === true) { Chris@4: // Files with fixed errors or warnings are F (green). Chris@4: // Files with unfixable errors or warnings are E (red). Chris@4: // Files with no errors or warnings are . (black). Chris@4: if ($fixable > 0) { Chris@4: if ($this->config->colors === true) { Chris@4: echo "\033[31m"; Chris@4: } Chris@4: Chris@4: echo 'E'; Chris@4: Chris@4: if ($this->config->colors === true) { Chris@4: echo "\033[0m"; Chris@4: } Chris@4: } else if ($fixed > 0) { Chris@4: if ($this->config->colors === true) { Chris@4: echo "\033[32m"; Chris@4: } Chris@4: Chris@4: echo 'F'; Chris@4: Chris@4: if ($this->config->colors === true) { Chris@4: echo "\033[0m"; Chris@4: } Chris@4: } else { Chris@4: echo '.'; Chris@4: }//end if Chris@4: } else { Chris@4: // Files with errors are E (red). Chris@4: // Files with fixable errors are E (green). Chris@4: // Files with warnings are W (yellow). Chris@4: // Files with fixable warnings are W (green). Chris@4: // Files with no errors or warnings are . (black). Chris@4: if ($errors > 0) { Chris@4: if ($this->config->colors === true) { Chris@4: if ($fixable > 0) { Chris@4: echo "\033[32m"; Chris@4: } else { Chris@4: echo "\033[31m"; Chris@4: } Chris@4: } Chris@4: Chris@4: echo 'E'; Chris@4: Chris@4: if ($this->config->colors === true) { Chris@4: echo "\033[0m"; Chris@4: } Chris@4: } else if ($warnings > 0) { Chris@4: if ($this->config->colors === true) { Chris@4: if ($fixable > 0) { Chris@4: echo "\033[32m"; Chris@4: } else { Chris@4: echo "\033[33m"; Chris@4: } Chris@4: } Chris@4: Chris@4: echo 'W'; Chris@4: Chris@4: if ($this->config->colors === true) { Chris@4: echo "\033[0m"; Chris@4: } Chris@4: } else { Chris@4: echo '.'; Chris@4: }//end if Chris@4: }//end if Chris@4: }//end if Chris@4: Chris@4: $numPerLine = 60; Chris@4: if ($numProcessed !== $numFiles && ($numProcessed % $numPerLine) !== 0) { Chris@4: return; Chris@4: } Chris@4: Chris@4: $percent = round(($numProcessed / $numFiles) * 100); Chris@4: $padding = (strlen($numFiles) - strlen($numProcessed)); Chris@4: if ($numProcessed === $numFiles && $numFiles > $numPerLine) { Chris@4: $padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine))); Chris@4: } Chris@4: Chris@4: echo str_repeat(' ', $padding)." $numProcessed / $numFiles ($percent%)".PHP_EOL; Chris@4: Chris@4: }//end printProgress() Chris@4: Chris@4: Chris@4: }//end class