/** * Generate summary information to be used during report generation. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed. * * @return array */ public function prepareFileReport(File $phpcsFile) { $report = array('filename' => Common::stripBasepath($phpcsFile->getFilename(), $this->config->basepath), 'errors' => $phpcsFile->getErrorCount(), 'warnings' => $phpcsFile->getWarningCount(), 'fixable' => $phpcsFile->getFixableCount(), 'messages' => array()); if ($report['errors'] === 0 && $report['warnings'] === 0) { // Prefect score! return $report; } if ($this->config->recordErrors === false) { $message = 'Errors are not being recorded but this report requires error messages. '; $message .= 'This report will not show the correct information.'; $report['messages'][1][1] = array(array('message' => $message, 'source' => 'Internal.RecordErrors', 'severity' => 5, 'fixable' => false, 'type' => 'ERROR')); return $report; } $errors = array(); // Merge errors and warnings. foreach ($phpcsFile->getErrors() as $line => $lineErrors) { foreach ($lineErrors as $column => $colErrors) { $newErrors = array(); foreach ($colErrors as $data) { $newErrors[] = array('message' => $data['message'], 'source' => $data['source'], 'severity' => $data['severity'], 'fixable' => $data['fixable'], 'type' => 'ERROR'); } $errors[$line][$column] = $newErrors; } ksort($errors[$line]); } //end foreach foreach ($phpcsFile->getWarnings() as $line => $lineWarnings) { foreach ($lineWarnings as $column => $colWarnings) { $newWarnings = array(); foreach ($colWarnings as $data) { $newWarnings[] = array('message' => $data['message'], 'source' => $data['source'], 'severity' => $data['severity'], 'fixable' => $data['fixable'], 'type' => 'WARNING'); } if (isset($errors[$line]) === false) { $errors[$line] = array(); } if (isset($errors[$line][$column]) === true) { $errors[$line][$column] = array_merge($newWarnings, $errors[$line][$column]); } else { $errors[$line][$column] = $newWarnings; } } //end foreach ksort($errors[$line]); } //end foreach ksort($errors); $report['messages'] = $errors; return $report; }
/** * Expands a ruleset reference into a list of sniff files. * * @param string $ref The reference from the ruleset XML file. * @param string $rulesetDir The directory of the ruleset XML file, used to * evaluate relative paths. * @param int $depth How many nested processing steps we are in. This * is only used for debug output. * * @return array * @throws RuntimeException If the reference is invalid. */ private function expandRulesetReference($ref, $rulesetDir, $depth = 0) { // Ignore internal sniffs codes as they are used to only // hide and change internal messages. if (substr($ref, 0, 9) === 'Internal.') { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* ignoring internal sniff code *" . PHP_EOL; } return array(); } // As sniffs can't begin with a full stop, assume references in // this format are relative paths and attempt to convert them // to absolute paths. If this fails, let the reference run through // the normal checks and have it fail as normal. if (substr($ref, 0, 1) === '.') { $realpath = Util\Common::realpath($rulesetDir . '/' . $ref); if ($realpath !== false) { $ref = $realpath; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } } // As sniffs can't begin with a tilde, assume references in // this format are relative to the user's home directory. if (substr($ref, 0, 2) === '~/') { $realpath = Util\Common::realpath($ref); if ($realpath !== false) { $ref = $realpath; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } } if (is_file($ref) === true) { if (substr($ref, -9) === 'Sniff.php') { // A single external sniff. $this->rulesetDirs[] = dirname(dirname(dirname($ref))); return array($ref); } } else { // See if this is a whole standard being referenced. $path = Util\Standards::getInstalledStandardPath($ref); if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { // If the ruleset exists inside the phar file, use it. if (file_exists($path . DIRECTORY_SEPARATOR . 'ruleset.xml') === true) { $path = $path . DIRECTORY_SEPARATOR . 'ruleset.xml'; } else { $path = null; } } if ($path !== null) { $ref = $path; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } else { if (is_dir($ref) === false) { // Work out the sniff path. $sepPos = strpos($ref, DIRECTORY_SEPARATOR); if ($sepPos !== false) { $stdName = substr($ref, 0, $sepPos); $path = substr($ref, $sepPos); } else { $parts = explode('.', $ref); $stdName = $parts[0]; if (count($parts) === 1) { // A whole standard? $path = ''; } else { if (count($parts) === 2) { // A directory of sniffs? $path = DIRECTORY_SEPARATOR . 'Sniffs' . DIRECTORY_SEPARATOR . $parts[1]; } else { // A single sniff? $path = DIRECTORY_SEPARATOR . 'Sniffs' . DIRECTORY_SEPARATOR . $parts[1] . DIRECTORY_SEPARATOR . $parts[2] . 'Sniff.php'; } } } $newRef = false; $stdPath = Util\Standards::getInstalledStandardPath($stdName); if ($stdPath !== null && $path !== '') { if (Util\Common::isPharFile($stdPath) === true && strpos($stdPath, 'ruleset.xml') === false) { // Phar files can only return the directory, // since ruleset can be omitted if building one standard. $newRef = Util\Common::realpath($stdPath . $path); } else { $newRef = Util\Common::realpath(dirname($stdPath) . $path); } } if ($newRef === false) { // The sniff is not locally installed, so check if it is being // referenced as a remote sniff outside the install. We do this // by looking through all directories where we have found ruleset // files before, looking for ones for this particular standard, // and seeing if it is in there. foreach ($this->rulesetDirs as $dir) { if (strtolower(basename($dir)) !== strtolower($stdName)) { continue; } $newRef = Util\Common::realpath($dir . $path); if ($newRef !== false) { $ref = $newRef; } } } else { $ref = $newRef; } if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } } //end if } //end if if (is_dir($ref) === true) { if (is_file($ref . DIRECTORY_SEPARATOR . 'ruleset.xml') === true) { // We are referencing an external coding standard. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* rule is referencing a standard using directory name; processing *" . PHP_EOL; } return $this->processRuleset($ref . DIRECTORY_SEPARATOR . 'ruleset.xml', $depth + 2); } else { // We are referencing a whole directory of sniffs. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* rule is referencing a directory of sniffs *" . PHP_EOL; echo str_repeat("\t", $depth); echo "\t\tAdding sniff files from directory" . PHP_EOL; } return $this->expandSniffDirectory($ref, $depth + 1); } } else { if (is_file($ref) === false) { $error = "Referenced sniff \"{$ref}\" does not exist"; throw new RuntimeException($error); } if (substr($ref, -9) === 'Sniff.php') { // A single sniff. return array($ref); } else { // Assume an external ruleset.xml file. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* rule is referencing a standard using ruleset path; processing *" . PHP_EOL; } return $this->processRuleset($ref, $depth + 2); } } //end if }
/** * Performs the run. * * @return int The number of errors and warnings found. */ private function run() { // The class that manages all reporters for the run. $this->reporter = new Reporter($this->config); // Include bootstrap files. foreach ($this->config->bootstrap as $bootstrap) { include $bootstrap; } if ($this->config->stdin === true) { $fileContents = $this->config->stdinContent; if ($fileContents === null) { $handle = fopen('php://stdin', 'r'); stream_set_blocking($handle, true); $fileContents = stream_get_contents($handle); fclose($handle); } $todo = new FileList($this->config, $this->ruleset); $dummy = new DummyFile($fileContents, $this->ruleset, $this->config); $todo->addFile($dummy->path, $dummy); $numFiles = 1; } else { if (empty($this->config->files) === true) { echo 'ERROR: You must supply at least one file or directory to process.' . PHP_EOL . PHP_EOL; $this->config->printUsage(); exit(0); } if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Creating file list... '; } $todo = new FileList($this->config, $this->ruleset); $numFiles = count($todo); if (PHP_CODESNIFFER_VERBOSITY > 0) { echo "DONE ({$numFiles} files in queue)" . PHP_EOL; } if ($this->config->cache === true) { if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Loading cache... '; } Cache::load($this->ruleset, $this->config); if (PHP_CODESNIFFER_VERBOSITY > 0) { $size = Cache::getSize(); echo "DONE ({$size} files in cache)" . PHP_EOL; } } } //end if // Turn all sniff errors into exceptions. set_error_handler(array($this, 'handleErrors')); // If verbosity is too high, turn off parallelism so the // debug output is clean. if (PHP_CODESNIFFER_VERBOSITY > 1) { $this->config->parallel = 1; } // If the PCNTL extension isn't installed, we can't fork. if (function_exists('pcntl_fork') === false) { $this->config->parallel = 1; } $lastDir = ''; if ($this->config->parallel === 1) { // Running normally. $numProcessed = 0; foreach ($todo as $path => $file) { $currDir = dirname($path); if ($lastDir !== $currDir) { if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Changing into directory ' . Common::stripBasepath($currDir, $this->config->basepath) . PHP_EOL; } $lastDir = $currDir; } $this->processFile($file); $numProcessed++; $this->printProgress($file, $numFiles, $numProcessed); } } else { // Batching and forking. $childProcs = array(); $numFiles = count($todo); $numPerBatch = ceil($numFiles / $this->config->parallel); for ($batch = 0; $batch < $this->config->parallel; $batch++) { $startAt = $batch * $numPerBatch; if ($startAt >= $numFiles) { break; } $endAt = $startAt + $numPerBatch; if ($endAt > $numFiles) { $endAt = $numFiles; } $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child'); $pid = pcntl_fork(); if ($pid === -1) { throw new RuntimeException('Failed to create child process'); } else { if ($pid !== 0) { $childProcs[] = array('pid' => $pid, 'out' => $childOutFilename); } else { // Move forward to the start of the batch. $todo->rewind(); for ($i = 0; $i < $startAt; $i++) { $todo->next(); } // Reset the reporter to make sure only figures from this // file batch are recorded. $this->reporter->totalFiles = 0; $this->reporter->totalErrors = 0; $this->reporter->totalWarnings = 0; $this->reporter->totalFixable = 0; // Process the files. $pathsProcessed = array(); ob_start(); for ($i = $startAt; $i < $endAt; $i++) { $path = $todo->key(); $file = $todo->current(); $currDir = dirname($path); if ($lastDir !== $currDir) { if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Changing into directory ' . Common::stripBasepath($currDir, $this->config->basepath) . PHP_EOL; } $lastDir = $currDir; } $this->processFile($file); $pathsProcessed[] = $path; $todo->next(); } $debugOutput = ob_get_contents(); ob_end_clean(); // Write information about the run to the filesystem // so it can be picked up by the main process. $childOutput = array('totalFiles' => $this->reporter->totalFiles, 'totalErrors' => $this->reporter->totalErrors, 'totalWarnings' => $this->reporter->totalWarnings, 'totalFixable' => $this->reporter->totalFixable, 'totalFixed' => $this->reporter->totalFixed); $output = '<' . '?php' . "\n" . ' $childOutput = '; $output .= var_export($childOutput, true); $output .= ";\n\$debugOutput = "; $output .= var_export($debugOutput, true); if ($this->config->cache === true) { $childCache = array(); foreach ($pathsProcessed as $path) { $childCache[$path] = Cache::get($path); } $output .= ";\n\$childCache = "; $output .= var_export($childCache, true); } $output .= ";\n?" . '>'; file_put_contents($childOutFilename, $output); exit($pid); } } //end if } //end for $this->processChildProcs($childProcs); } //end if restore_error_handler(); if (PHP_CODESNIFFER_VERBOSITY === 0 && $this->config->interactive === false && $this->config->showProgress === true) { echo PHP_EOL . PHP_EOL; } if ($this->config->cache === true) { Cache::save(); } $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit'); $ignoreErrors = Config::getConfigData('ignore_errors_on_exit'); $return = $this->reporter->totalErrors + $this->reporter->totalWarnings; if ($ignoreErrors !== null) { $ignoreErrors = (bool) $ignoreErrors; if ($ignoreErrors === true) { $return -= $this->reporter->totalErrors; } } if ($ignoreWarnings !== null) { $ignoreWarnings = (bool) $ignoreWarnings; if ($ignoreWarnings === true) { $return -= $this->reporter->totalWarnings; } } return $return; }