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