* @copyright 2006-2011 Squiz Pty Ltd (ABN 77 084 670 600) * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence * @link http://pear.php.net/package/PHP_CodeSniffer */ if (is_file(dirname(__FILE__).'/../CodeSniffer.php') === true) { include_once dirname(__FILE__).'/../CodeSniffer.php'; } else { include_once 'PHP/CodeSniffer.php'; } /** * A class to process command line phpcs scripts. * * @category PHP * @package PHP_CodeSniffer * @author Greg Sherwood * @copyright 2006-2011 Squiz Pty Ltd (ABN 77 084 670 600) * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence * @version Release: 1.3.3 * @link http://pear.php.net/package/PHP_CodeSniffer */ class PHP_CodeSniffer_CLI { /** * An array of all values specified on the command line. * * @var array */ protected $values = array(); /** * The minimum severity level errors must have to be displayed. * * @var bool */ public $errorSeverity = 0; /** * The minimum severity level warnings must have to be displayed. * * @var bool */ public $warningSeverity = 0; /** * Exits if the minimum requirements of PHP_CodSniffer are not met. * * @return array */ public function checkRequirements() { // Check the PHP version. if (version_compare(PHP_VERSION, '5.1.2') === -1) { echo 'ERROR: PHP_CodeSniffer requires PHP version 5.1.2 or greater.'.PHP_EOL; exit(2); } if (extension_loaded('tokenizer') === false) { echo 'ERROR: PHP_CodeSniffer requires the tokenizer extension to be enabled.'.PHP_EOL; exit(2); } }//end checkRequirements() /** * Get a list of default values for all possible command line arguments. * * @return array */ public function getDefaults() { // The default values for config settings. $defaults['files'] = array(); $defaults['standard'] = null; $defaults['verbosity'] = 0; $defaults['interactive'] = false; $defaults['local'] = false; $defaults['showSources'] = false; $defaults['extensions'] = array(); $defaults['sniffs'] = array(); $defaults['ignored'] = array(); $defaults['reportFile'] = null; $defaults['generator'] = ''; $defaults['reports'] = array(); $reportFormat = PHP_CodeSniffer::getConfigData('report_format'); if ($reportFormat !== null) { $defaults['reports'][$reportFormat] = null; } $defaults['warningSeverity'] = null; $showWarnings = PHP_CodeSniffer::getConfigData('show_warnings'); if ($showWarnings !== null) { $showWarnings = (bool) $showWarnings; if ($showWarnings === false) { $defaults['warningSeverity'] = 0; } } $tabWidth = PHP_CodeSniffer::getConfigData('tab_width'); if ($tabWidth === null) { $defaults['tabWidth'] = 0; } else { $defaults['tabWidth'] = (int) $tabWidth; } $encoding = PHP_CodeSniffer::getConfigData('encoding'); if ($encoding === null) { $defaults['encoding'] = 'iso-8859-1'; } else { $defaults['encoding'] = strtolower($encoding); } $severity = PHP_CodeSniffer::getConfigData('severity'); if ($severity === null) { $defaults['errorSeverity'] = null; $defaults['warningSeverity'] = null; } else { $defaults['errorSeverity'] = (int) $severity; $defaults['warningSeverity'] = (int) $severity; } $severity = PHP_CodeSniffer::getConfigData('error_severity'); if ($severity === null) { $defaults['errorSeverity'] = null; } else { $defaults['errorSeverity'] = (int) $severity; } $severity = PHP_CodeSniffer::getConfigData('warning_severity'); if ($severity === null) { $defaults['warningSeverity'] = null; } else { $defaults['warningSeverity'] = (int) $severity; } $reportWidth = PHP_CodeSniffer::getConfigData('report_width'); if ($reportWidth === null) { $defaults['reportWidth'] = 80; } else { $defaults['reportWidth'] = (int) $reportWidth; } $showProgress = PHP_CodeSniffer::getConfigData('show_progress'); if ($showProgress === null) { $defaults['showProgress'] = false; } else { $defaults['showProgress'] = (bool) $showProgress; } return $defaults; }//end getDefaults() /** * Process the command line arguments and returns the values. * * @return array */ public function getCommandLineValues() { if (empty($this->values) === false) { return $this->values; } $values = $this->getDefaults(); for ($i = 1; $i < $_SERVER['argc']; $i++) { $arg = $_SERVER['argv'][$i]; if ($arg === '') { continue; } if ($arg{0} === '-') { if ($arg === '-' || $arg === '--') { // Empty argument, ignore it. continue; } if ($arg{1} === '-') { $values = $this->processLongArgument(substr($arg, 2), $i, $values); } else { $switches = str_split($arg); foreach ($switches as $switch) { if ($switch === '-') { continue; } $values = $this->processShortArgument($switch, $i, $values); } } } else { $values = $this->processUnknownArgument($arg, $i, $values); }//end if }//end for $this->values = $values; return $values; }//end getCommandLineValues() /** * Processes a short (-e) command line argument. * * @param string $arg The command line argument. * @param int $pos The position of the argument on the command line. * @param array $values An array of values determined from CLI args. * * @return array The updated CLI values. * @see getCommandLineValues() */ public function processShortArgument($arg, $pos, $values) { switch ($arg) { case 'h': case '?': $this->printUsage(); exit(0); break; case 'i' : $this->printInstalledStandards(); exit(0); break; case 'v' : $values['verbosity']++; break; case 'l' : $values['local'] = true; break; case 's' : $values['showSources'] = true; break; case 'a' : $values['interactive'] = true; break; case 'p' : $values['showProgress'] = true; break; case 'd' : $ini = explode('=', $_SERVER['argv'][($pos + 1)]); $_SERVER['argv'][($pos + 1)] = ''; if (isset($ini[1]) === true) { ini_set($ini[0], $ini[1]); } else { ini_set($ini[0], true); } break; case 'n' : $values['warningSeverity'] = 0; break; case 'w' : $values['warningSeverity'] = null; break; default: $values = $this->processUnknownArgument('-'.$arg, $pos, $values); }//end switch return $values; }//end processShortArgument() /** * Processes a long (--example) command line argument. * * @param string $arg The command line argument. * @param int $pos The position of the argument on the command line. * @param array $values An array of values determined from CLI args. * * @return array The updated CLI values. * @see getCommandLineValues() */ public function processLongArgument($arg, $pos, $values) { switch ($arg) { case 'help': $this->printUsage(); exit(0); break; case 'version': echo 'PHP_CodeSniffer version 1.3.3 (stable) '; echo 'by Squiz Pty Ltd. (http://www.squiz.net)'.PHP_EOL; exit(0); break; case 'config-set': $key = $_SERVER['argv'][($pos + 1)]; $value = $_SERVER['argv'][($pos + 2)]; PHP_CodeSniffer::setConfigData($key, $value); exit(0); break; case 'config-delete': $key = $_SERVER['argv'][($pos + 1)]; PHP_CodeSniffer::setConfigData($key, null); exit(0); break; case 'config-show': $data = PHP_CodeSniffer::getAllConfigData(); print_r($data); exit(0); break; default: if (substr($arg, 0, 7) === 'sniffs=') { $values['sniffs'] = array(); $sniffs = substr($arg, 7); $sniffs = explode(',', $sniffs); // Convert the sniffs to class names. foreach ($sniffs as $sniff) { $parts = explode('.', $sniff); $values['sniffs'][] = $parts[0].'_Sniffs_'.$parts[1].'_'.$parts[2].'Sniff'; } } else if (substr($arg, 0, 12) === 'report-file=') { $values['reportFile'] = realpath(substr($arg, 12)); // It may not exist and return false instead. if ($values['reportFile'] === false) { $values['reportFile'] = substr($arg, 12); } if (is_dir($values['reportFile']) === true) { echo 'ERROR: The specified report file path "'.$values['reportFile'].'" is a directory.'.PHP_EOL.PHP_EOL; $this->printUsage(); exit(2); } $dir = dirname($values['reportFile']); if (is_dir($dir) === false) { echo 'ERROR: The specified report file path "'.$values['reportFile'].'" points to a non-existent directory.'.PHP_EOL.PHP_EOL; $this->printUsage(); exit(2); } } else if (substr($arg, 0, 13) === 'report-width=') { $values['reportWidth'] = (int) substr($arg, 13); } else if (substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-' ) { if ($arg[6] === '-') { // This is a report with file output. $split = strpos($arg, '='); if ($split === false) { $report = substr($arg, 7); $output = null; } else { $report = substr($arg, 7, ($split - 7)); $output = substr($arg, ($split + 1)); if ($output === false) { $output = null; } } } else { // This is a single report. $report = substr($arg, 7); $output = null; } $validReports = array( 'full', 'xml', 'checkstyle', 'csv', 'emacs', 'source', 'summary', 'svnblame', 'gitblame', 'hgblame', ); if (in_array($report, $validReports) === false) { echo 'ERROR: Report type "'.$report.'" not known.'.PHP_EOL; exit(2); } $values['reports'][$report] = $output; } else if (substr($arg, 0, 9) === 'standard=') { $values['standard'] = substr($arg, 9); } else if (substr($arg, 0, 11) === 'extensions=') { $values['extensions'] = explode(',', substr($arg, 11)); } else if (substr($arg, 0, 9) === 'severity=') { $values['errorSeverity'] = (int) substr($arg, 9); $values['warningSeverity'] = $values['errorSeverity']; } else if (substr($arg, 0, 15) === 'error-severity=') { $values['errorSeverity'] = (int) substr($arg, 15); } else if (substr($arg, 0, 17) === 'warning-severity=') { $values['warningSeverity'] = (int) substr($arg, 17); } else if (substr($arg, 0, 7) === 'ignore=') { // Split the ignore string on commas, unless the comma is escaped // using 1 or 3 slashes (\, or \\\,). $values['ignored'] = preg_split( '/(?<=(?processUnknownArgument('--'.$arg, $pos, $values); }//end if break; }//end switch return $values; }//end processLongArgument() /** * Processes an unknown command line argument. * * Assumes all unknown arguments are files and folders to check. * * @param string $arg The command line argument. * @param int $pos The position of the argument on the command line. * @param array $values An array of values determined from CLI args. * * @return array The updated CLI values. * @see getCommandLineValues() */ public function processUnknownArgument($arg, $pos, $values) { // We don't know about any additional switches; just files. if ($arg{0} === '-') { echo 'ERROR: option "'.$arg.'" not known.'.PHP_EOL.PHP_EOL; $this->printUsage(); exit(2); } $file = realpath($arg); if (file_exists($file) === false) { echo 'ERROR: The file "'.$arg.'" does not exist.'.PHP_EOL.PHP_EOL; $this->printUsage(); exit(2); } else { $values['files'][] = $file; } return $values; }//end processUnknownArgument() /** * Runs PHP_CodeSniffer over files and directories. * * @param array $values An array of values determined from CLI args. * * @return int The number of error and warning messages shown. * @see getCommandLineValues() */ public function process($values=array()) { if (empty($values) === true) { $values = $this->getCommandLineValues(); } if ($values['generator'] !== '') { $phpcs = new PHP_CodeSniffer($values['verbosity']); $phpcs->generateDocs( $values['standard'], $values['files'], $values['generator'] ); exit(0); } $fileContents = ''; if (empty($values['files']) === true) { // Check if they passing in the file contents. $handle = fopen('php://stdin', 'r'); $fileContents = stream_get_contents($handle); fclose($handle); if ($fileContents === '') { // No files and no content passed in. echo 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL; $this->printUsage(); exit(2); } } $values['standard'] = $this->validateStandard($values['standard']); if (PHP_CodeSniffer::isInstalledStandard($values['standard']) === false) { // They didn't select a valid coding standard, so help them // out by letting them know which standards are installed. echo 'ERROR: the "'.$values['standard'].'" coding standard is not installed. '; $this->printInstalledStandards(); exit(2); } $phpcs = new PHP_CodeSniffer( $values['verbosity'], $values['tabWidth'], $values['encoding'], $values['interactive'] ); // Set file extensions if they were specified. Otherwise, // let PHP_CodeSniffer decide on the defaults. if (empty($values['extensions']) === false) { $phpcs->setAllowedFileExtensions($values['extensions']); } // Set ignore patterns if they were specified. if (empty($values['ignored']) === false) { $phpcs->setIgnorePatterns($values['ignored']); } // Set some convenience member vars. if ($values['errorSeverity'] === null) { $this->errorSeverity = PHPCS_DEFAULT_ERROR_SEV; } else { $this->errorSeverity = $values['errorSeverity']; } if ($values['warningSeverity'] === null) { $this->warningSeverity = PHPCS_DEFAULT_WARN_SEV; } else { $this->warningSeverity = $values['warningSeverity']; } $phpcs->setCli($this); $phpcs->process( $values['files'], $values['standard'], $values['sniffs'], $values['local'] ); if ($fileContents !== '') { $phpcs->processFile('STDIN', $fileContents); } return $this->printErrorReport( $phpcs, $values['reports'], $values['showSources'], $values['reportFile'], $values['reportWidth'] ); }//end process() /** * Prints the error report for the run. * * Note that this function may actually print multiple reports * as the user may have specified a number of output formats. * * @param PHP_CodeSniffer $phpcs The PHP_CodeSniffer object containing * the errors. * @param array $reports A list of reports to print. * @param bool $showSources TRUE if report should show error sources * (not used by all reports). * @param string $reportFile A default file to log report output to. * @param int $reportWidth How wide the screen reports should be. * * @return int The number of error and warning messages shown. */ public function printErrorReport( PHP_CodeSniffer $phpcs, $reports, $showSources, $reportFile, $reportWidth ) { $reporting = new PHP_CodeSniffer_Reporting(); $filesViolations = $phpcs->getFilesErrors(); if (empty($reports) === true) { $reports['full'] = $reportFile; } $errors = 0; $toScreen = false; foreach ($reports as $report => $output) { if ($output === null) { $output = $reportFile; } if ($reportFile === null) { $toScreen = true; } // We don't add errors here because the number of // errors reported by each report type will always be the // same, so we really just need 1 number. $errors = $reporting->printReport( $report, $filesViolations, $showSources, $output, $reportWidth ); } // Only print PHP_Timer output if no reports were // printed to the screen so we don't put additional output // in something like an XML report. If we are printing to screen, // the report types would have already worked out who should // print the timer info. if ($toScreen === false && PHP_CODESNIFFER_INTERACTIVE === false && class_exists('PHP_Timer', false) === true ) { echo PHP_Timer::resourceUsage().PHP_EOL.PHP_EOL; } // They should all return the same value, so it // doesn't matter which return value we end up using. return $errors; }//end printErrorReport() /** * Convert the passed standard into a valid standard. * * Checks things like default values and case. * * @param string $standard The standard to validate. * * @return string */ public function validateStandard($standard) { if ($standard === null) { // They did not supply a standard to use. // Try to get the default from the config system. $standard = PHP_CodeSniffer::getConfigData('default_standard'); if ($standard === null) { $standard = 'PEAR'; } } // Check if the standard name is valid. If not, check that the case // was not entered incorrectly. if (PHP_CodeSniffer::isInstalledStandard($standard) === false) { $installedStandards = PHP_CodeSniffer::getInstalledStandards(); foreach ($installedStandards as $validStandard) { if (strtolower($standard) === strtolower($validStandard)) { $standard = $validStandard; break; } } } return $standard; }//end validateStandard() /** * Prints out the usage information for this script. * * @return void */ public function printUsage() { echo 'Usage: phpcs [-nwlsapvi] [-d key[=value]]'.PHP_EOL; echo ' [--report=] [--report-file=] [--report-=] ...'.PHP_EOL; echo ' [--report-width=] [--generator=] [--tab-width=]'.PHP_EOL; echo ' [--severity=] [--error-severity=] [--warning-severity=]'.PHP_EOL; echo ' [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL; echo ' [--standard=] [--sniffs=] [--encoding=]'.PHP_EOL; echo ' [--extensions=] [--ignore=] ...'.PHP_EOL; echo ' -n Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL; echo ' -w Print both warnings and errors (on by default)'.PHP_EOL; echo ' -l Local directory only, no recursion'.PHP_EOL; echo ' -s Show sniff codes in all reports'.PHP_EOL; echo ' -a Run interactively'.PHP_EOL; echo ' -p Show progress of the run'.PHP_EOL; echo ' -v[v][v] Print verbose output'.PHP_EOL; echo ' -i Show a list of installed coding standards'.PHP_EOL; echo ' -d Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL; echo ' --help Print this help message'.PHP_EOL; echo ' --version Print version information'.PHP_EOL; echo ' One or more files and/or directories to check'.PHP_EOL; echo ' A comma separated list of file extensions to check'.PHP_EOL; echo ' (only valid if checking a directory)'.PHP_EOL; echo ' A comma separated list of patterns to ignore files and directories'.PHP_EOL; echo ' The encoding of the files being checked (default is iso-8859-1)'.PHP_EOL; echo ' A comma separated list of sniff codes to limit the check to'.PHP_EOL; echo ' (all sniffs must be part of the specified standard)'.PHP_EOL; echo ' The minimum severity required to display an error or warning'.PHP_EOL; echo ' The name or path of the coding standard to use'.PHP_EOL; echo ' The number of spaces each tab represents'.PHP_EOL; echo ' The name of a doc generator to use'.PHP_EOL; echo ' (forces doc generation instead of checking)'.PHP_EOL; echo ' Print either the "full", "xml", "checkstyle", "csv", "emacs"'.PHP_EOL; echo ' "source", "summary", "svnblame", "gitblame" or "hgblame" report'.PHP_EOL; echo ' (the "full" report is printed by default)'.PHP_EOL; echo ' Write the report to the specified file path'.PHP_EOL; echo ' How many columns wide screen reports should be printed'.PHP_EOL; }//end printUsage() /** * Prints out a list of installed coding standards. * * @return void */ public function printInstalledStandards() { $installedStandards = PHP_CodeSniffer::getInstalledStandards(); $numStandards = count($installedStandards); if ($numStandards === 0) { echo 'No coding standards are installed.'.PHP_EOL; } else { $lastStandard = array_pop($installedStandards); if ($numStandards === 1) { echo "The only coding standard installed is $lastStandard".PHP_EOL; } else { $standardList = implode(', ', $installedStandards); $standardList .= ' and '.$lastStandard; echo 'The installed coding standards are '.$standardList.PHP_EOL; } } }//end printInstalledStandards() }//end class ?>