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\Exceptions\RuntimeException; Chris@17: use PHP_CodeSniffer\Exceptions\DeepExitException; Chris@17: Chris@17: class Config Chris@17: { Chris@17: Chris@17: /** Chris@17: * The current version. Chris@17: * Chris@17: * @var string Chris@17: */ Chris@18: const VERSION = '3.4.2'; Chris@17: Chris@17: /** Chris@17: * Package stability; either stable, beta or alpha. Chris@17: * Chris@17: * @var string Chris@17: */ Chris@17: const STABILITY = 'stable'; Chris@17: Chris@17: /** Chris@17: * An array of settings that PHPCS and PHPCBF accept. Chris@17: * Chris@17: * This array is not meant to be accessed directly. Instead, use the settings Chris@17: * as if they are class member vars so the __get() and __set() magic methods Chris@17: * can be used to validate the values. For example, to set the verbosity level to Chris@17: * level 2, use $this->verbosity = 2; instead of accessing this property directly. Chris@17: * Chris@17: * The list of settings are: Chris@17: * Chris@17: * string[] files The files and directories to check. Chris@17: * string[] standards The standards being used for checking. Chris@17: * int verbosity How verbose the output should be. Chris@17: * 0: no unnecessary output Chris@17: * 1: basic output for files being checked Chris@17: * 2: ruleset and file parsing output Chris@17: * 3: sniff execution output Chris@17: * bool interactive Enable interactive checking mode. Chris@17: * bool parallel Check files in parallel. Chris@17: * bool cache Enable the use of the file cache. Chris@17: * bool cacheFile A file where the cache data should be written Chris@17: * bool colors Display colours in output. Chris@17: * bool explain Explain the coding standards. Chris@17: * bool local Process local files in directories only (no recursion). Chris@17: * bool showSources Show sniff source codes in report output. Chris@17: * bool showProgress Show basic progress information while running. Chris@17: * bool quiet Quiet mode; disables progress and verbose output. Chris@17: * bool annotations Process phpcs: annotations. Chris@17: * int tabWidth How many spaces each tab is worth. Chris@17: * string encoding The encoding of the files being checked. Chris@17: * string[] sniffs The sniffs that should be used for checking. Chris@17: * If empty, all sniffs in the supplied standards will be used. Chris@17: * string[] exclude The sniffs that should be excluded from checking. Chris@17: * If empty, all sniffs in the supplied standards will be used. Chris@17: * string[] ignored Regular expressions used to ignore files and folders during checking. Chris@17: * string reportFile A file where the report output should be written. Chris@17: * string generator The documentation generator to use. Chris@17: * string filter The filter to use for the run. Chris@17: * string[] bootstrap One of more files to include before the run begins. Chris@17: * int reportWidth The maximum number of columns that reports should use for output. Chris@17: * Set to "auto" for have this value changed to the width of the terminal. Chris@17: * int errorSeverity The minimum severity an error must have to be displayed. Chris@17: * int warningSeverity The minimum severity a warning must have to be displayed. Chris@17: * bool recordErrors Record the content of error messages as well as error counts. Chris@17: * string suffix A suffix to add to fixed files. Chris@17: * string basepath A file system location to strip from the paths of files shown in reports. Chris@17: * bool stdin Read content from STDIN instead of supplied files. Chris@17: * string stdinContent Content passed directly to PHPCS on STDIN. Chris@17: * string stdinPath The path to use for content passed on STDIN. Chris@17: * Chris@17: * array extensions File extensions that should be checked, and what tokenizer to use. Chris@17: * E.g., array('inc' => 'PHP'); Chris@17: * array reports The reports to use for printing output after the run. Chris@17: * The format of the array is: Chris@17: * array( Chris@17: * 'reportName1' => 'outputFile', Chris@17: * 'reportName2' => null, Chris@17: * ); Chris@17: * If the array value is NULL, the report will be written to the screen. Chris@17: * Chris@17: * string[] unknown Any arguments gathered on the command line that are unknown to us. Chris@17: * E.g., using `phpcs -c` will give array('c'); Chris@17: * Chris@17: * @var array Chris@17: */ Chris@17: private $settings = [ Chris@17: 'files' => null, Chris@17: 'standards' => null, Chris@17: 'verbosity' => null, Chris@17: 'interactive' => null, Chris@17: 'parallel' => null, Chris@17: 'cache' => null, Chris@17: 'cacheFile' => null, Chris@17: 'colors' => null, Chris@17: 'explain' => null, Chris@17: 'local' => null, Chris@17: 'showSources' => null, Chris@17: 'showProgress' => null, Chris@17: 'quiet' => null, Chris@17: 'annotations' => null, Chris@17: 'tabWidth' => null, Chris@17: 'encoding' => null, Chris@17: 'extensions' => null, Chris@17: 'sniffs' => null, Chris@17: 'exclude' => null, Chris@17: 'ignored' => null, Chris@17: 'reportFile' => null, Chris@17: 'generator' => null, Chris@17: 'filter' => null, Chris@17: 'bootstrap' => null, Chris@17: 'reports' => null, Chris@17: 'basepath' => null, Chris@17: 'reportWidth' => null, Chris@17: 'errorSeverity' => null, Chris@17: 'warningSeverity' => null, Chris@17: 'recordErrors' => null, Chris@17: 'suffix' => null, Chris@17: 'stdin' => null, Chris@17: 'stdinContent' => null, Chris@17: 'stdinPath' => null, Chris@17: 'unknown' => null, Chris@17: ]; Chris@17: Chris@17: /** Chris@17: * Whether or not to kill the process when an unknown command line arg is found. Chris@17: * Chris@17: * If FALSE, arguments that are not command line options or file/directory paths Chris@17: * will be ignored and execution will continue. These values will be stored in Chris@17: * $this->unknown. Chris@17: * Chris@17: * @var boolean Chris@17: */ Chris@17: public $dieOnUnknownArg; Chris@17: Chris@17: /** Chris@17: * The current command line arguments we are processing. Chris@17: * Chris@17: * @var string[] Chris@17: */ Chris@17: private $cliArgs = []; Chris@17: Chris@17: /** Chris@17: * Command line values that the user has supplied directly. Chris@17: * Chris@17: * @var array Chris@17: */ Chris@17: private static $overriddenDefaults = []; Chris@17: Chris@17: /** Chris@17: * Config file data that has been loaded for the run. Chris@17: * Chris@17: * @var array Chris@17: */ Chris@17: private static $configData = null; Chris@17: Chris@17: /** Chris@17: * The full path to the config data file that has been loaded. Chris@17: * Chris@17: * @var string Chris@17: */ Chris@17: private static $configDataFile = null; Chris@17: Chris@17: /** Chris@17: * Automatically discovered executable utility paths. Chris@17: * Chris@17: * @var array Chris@17: */ Chris@17: private static $executablePaths = []; Chris@17: Chris@17: Chris@17: /** Chris@17: * Get the value of an inaccessible property. Chris@17: * Chris@17: * @param string $name The name of the property. Chris@17: * Chris@17: * @return mixed Chris@18: * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid. Chris@17: */ Chris@17: public function __get($name) Chris@17: { Chris@17: if (array_key_exists($name, $this->settings) === false) { Chris@17: throw new RuntimeException("ERROR: unable to get value of property \"$name\""); Chris@17: } Chris@17: Chris@17: return $this->settings[$name]; Chris@17: Chris@17: }//end __get() Chris@17: Chris@17: Chris@17: /** Chris@17: * Set the value of an inaccessible property. Chris@17: * Chris@17: * @param string $name The name of the property. Chris@17: * @param mixed $value The value of the property. Chris@17: * Chris@17: * @return void Chris@18: * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid. Chris@17: */ Chris@17: public function __set($name, $value) Chris@17: { Chris@17: if (array_key_exists($name, $this->settings) === false) { Chris@17: throw new RuntimeException("Can't __set() $name; setting doesn't exist"); Chris@17: } Chris@17: Chris@17: switch ($name) { Chris@17: case 'reportWidth' : Chris@17: // Support auto terminal width. Chris@17: if ($value === 'auto' && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1) { Chris@17: $value = (int) $matches[1]; Chris@17: } else { Chris@17: $value = (int) $value; Chris@17: } Chris@17: break; Chris@17: case 'standards' : Chris@17: $cleaned = []; Chris@17: Chris@17: // Check if the standard name is valid, or if the case is invalid. Chris@17: $installedStandards = Util\Standards::getInstalledStandards(); Chris@17: foreach ($value as $standard) { Chris@17: foreach ($installedStandards as $validStandard) { Chris@17: if (strtolower($standard) === strtolower($validStandard)) { Chris@17: $standard = $validStandard; Chris@17: break; Chris@17: } Chris@17: } Chris@17: Chris@17: $cleaned[] = $standard; Chris@17: } Chris@17: Chris@17: $value = $cleaned; Chris@17: break; Chris@17: default : Chris@17: // No validation required. Chris@17: break; Chris@17: }//end switch Chris@17: Chris@17: $this->settings[$name] = $value; Chris@17: Chris@17: }//end __set() Chris@17: Chris@17: Chris@17: /** Chris@17: * Check if the value of an inaccessible property is set. Chris@17: * Chris@17: * @param string $name The name of the property. Chris@17: * Chris@17: * @return bool Chris@17: */ Chris@17: public function __isset($name) Chris@17: { Chris@17: return isset($this->settings[$name]); Chris@17: Chris@17: }//end __isset() Chris@17: Chris@17: Chris@17: /** Chris@17: * Unset the value of an inaccessible property. Chris@17: * Chris@17: * @param string $name The name of the property. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function __unset($name) Chris@17: { Chris@17: $this->settings[$name] = null; Chris@17: Chris@17: }//end __unset() Chris@17: Chris@17: Chris@17: /** Chris@17: * Get the array of all config settings. Chris@17: * Chris@17: * @return array Chris@17: */ Chris@17: public function getSettings() Chris@17: { Chris@17: return $this->settings; Chris@17: Chris@17: }//end getSettings() Chris@17: Chris@17: Chris@17: /** Chris@17: * Set the array of all config settings. Chris@17: * Chris@17: * @param array $settings The array of config settings. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function setSettings($settings) Chris@17: { Chris@17: return $this->settings = $settings; Chris@17: Chris@17: }//end setSettings() Chris@17: Chris@17: Chris@17: /** Chris@17: * Creates a Config object and populates it with command line values. Chris@17: * Chris@17: * @param array $cliArgs An array of values gathered from CLI args. Chris@17: * @param bool $dieOnUnknownArg Whether or not to kill the process when an Chris@17: * unknown command line arg is found. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function __construct(array $cliArgs=[], $dieOnUnknownArg=true) Chris@17: { Chris@17: if (defined('PHP_CODESNIFFER_IN_TESTS') === true) { Chris@17: // Let everything through during testing so that we can Chris@17: // make use of PHPUnit command line arguments as well. Chris@17: $this->dieOnUnknownArg = false; Chris@17: } else { Chris@17: $this->dieOnUnknownArg = $dieOnUnknownArg; Chris@17: } Chris@17: Chris@17: if (empty($cliArgs) === true) { Chris@17: $cliArgs = $_SERVER['argv']; Chris@17: array_shift($cliArgs); Chris@17: } Chris@17: Chris@17: $this->restoreDefaults(); Chris@17: $this->setCommandLineValues($cliArgs); Chris@17: Chris@17: if (isset(self::$overriddenDefaults['standards']) === false) { Chris@17: // They did not supply a standard to use. Chris@17: // Look for a default ruleset in the current directory or higher. Chris@17: $currentDir = getcwd(); Chris@17: Chris@17: $defaultFiles = [ Chris@17: '.phpcs.xml', Chris@17: 'phpcs.xml', Chris@17: '.phpcs.xml.dist', Chris@17: 'phpcs.xml.dist', Chris@17: ]; Chris@17: Chris@17: do { Chris@17: foreach ($defaultFiles as $defaultFilename) { Chris@17: $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename; Chris@17: if (is_file($default) === true) { Chris@17: $this->standards = [$default]; Chris@17: break(2); Chris@17: } Chris@17: } Chris@17: Chris@17: $lastDir = $currentDir; Chris@17: $currentDir = dirname($currentDir); Chris@17: } while ($currentDir !== '.' && $currentDir !== $lastDir); Chris@17: }//end if Chris@17: Chris@17: if (defined('STDIN') === false Chris@17: || strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' Chris@17: ) { Chris@17: return; Chris@17: } Chris@17: Chris@17: $handle = fopen('php://stdin', 'r'); Chris@17: Chris@17: // Check for content on STDIN. Chris@17: if ($this->stdin === true Chris@17: || (Util\Common::isStdinATTY() === false Chris@17: && feof($handle) === false) Chris@17: ) { Chris@17: $readStreams = [$handle]; Chris@17: $writeSteams = null; Chris@17: Chris@17: $fileContents = ''; Chris@17: while (is_resource($handle) === true && feof($handle) === false) { Chris@17: // Set a timeout of 200ms. Chris@17: if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $fileContents .= fgets($handle); Chris@17: } Chris@17: Chris@17: if (trim($fileContents) !== '') { Chris@17: $this->stdin = true; Chris@17: $this->stdinContent = $fileContents; Chris@17: self::$overriddenDefaults['stdin'] = true; Chris@17: self::$overriddenDefaults['stdinContent'] = true; Chris@17: } Chris@17: }//end if Chris@17: Chris@17: fclose($handle); Chris@17: Chris@17: }//end __construct() Chris@17: Chris@17: Chris@17: /** Chris@17: * Set the command line values. Chris@17: * Chris@17: * @param array $args An array of command line arguments to set. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function setCommandLineValues($args) Chris@17: { Chris@17: $this->cliArgs = $args; Chris@17: $numArgs = count($args); Chris@17: Chris@17: for ($i = 0; $i < $numArgs; $i++) { Chris@17: $arg = $this->cliArgs[$i]; Chris@17: if ($arg === '') { Chris@17: continue; Chris@17: } Chris@17: Chris@17: if ($arg{0} === '-') { Chris@17: if ($arg === '-') { Chris@17: // Asking to read from STDIN. Chris@17: $this->stdin = true; Chris@17: self::$overriddenDefaults['stdin'] = true; Chris@17: continue; Chris@17: } Chris@17: Chris@17: if ($arg === '--') { Chris@17: // Empty argument, ignore it. Chris@17: continue; Chris@17: } Chris@17: Chris@17: if ($arg{1} === '-') { Chris@17: $this->processLongArgument(substr($arg, 2), $i); Chris@17: } else { Chris@17: $switches = str_split($arg); Chris@17: foreach ($switches as $switch) { Chris@17: if ($switch === '-') { Chris@17: continue; Chris@17: } Chris@17: Chris@17: $this->processShortArgument($switch, $i); Chris@17: } Chris@17: } Chris@17: } else { Chris@17: $this->processUnknownArgument($arg, $i); Chris@17: }//end if Chris@17: }//end for Chris@17: Chris@17: }//end setCommandLineValues() Chris@17: Chris@17: Chris@17: /** Chris@17: * Restore default values for all possible command line arguments. Chris@17: * Chris@17: * @return array Chris@17: */ Chris@17: public function restoreDefaults() Chris@17: { Chris@17: $this->files = []; Chris@17: $this->standards = ['PEAR']; Chris@17: $this->verbosity = 0; Chris@17: $this->interactive = false; Chris@17: $this->cache = false; Chris@17: $this->cacheFile = null; Chris@17: $this->colors = false; Chris@17: $this->explain = false; Chris@17: $this->local = false; Chris@17: $this->showSources = false; Chris@17: $this->showProgress = false; Chris@17: $this->quiet = false; Chris@17: $this->annotations = true; Chris@17: $this->parallel = 1; Chris@17: $this->tabWidth = 0; Chris@17: $this->encoding = 'utf-8'; Chris@17: $this->extensions = [ Chris@17: 'php' => 'PHP', Chris@17: 'inc' => 'PHP', Chris@17: 'js' => 'JS', Chris@17: 'css' => 'CSS', Chris@17: ]; Chris@17: $this->sniffs = []; Chris@17: $this->exclude = []; Chris@17: $this->ignored = []; Chris@17: $this->reportFile = null; Chris@17: $this->generator = null; Chris@17: $this->filter = null; Chris@17: $this->bootstrap = []; Chris@17: $this->basepath = null; Chris@17: $this->reports = ['full' => null]; Chris@17: $this->reportWidth = 'auto'; Chris@17: $this->errorSeverity = 5; Chris@17: $this->warningSeverity = 5; Chris@17: $this->recordErrors = true; Chris@17: $this->suffix = ''; Chris@17: $this->stdin = false; Chris@17: $this->stdinContent = null; Chris@17: $this->stdinPath = null; Chris@17: $this->unknown = []; Chris@17: Chris@17: $standard = self::getConfigData('default_standard'); Chris@17: if ($standard !== null) { Chris@17: $this->standards = explode(',', $standard); Chris@17: } Chris@17: Chris@17: $reportFormat = self::getConfigData('report_format'); Chris@17: if ($reportFormat !== null) { Chris@17: $this->reports = [$reportFormat => null]; Chris@17: } Chris@17: Chris@17: $tabWidth = self::getConfigData('tab_width'); Chris@17: if ($tabWidth !== null) { Chris@17: $this->tabWidth = (int) $tabWidth; Chris@17: } Chris@17: Chris@17: $encoding = self::getConfigData('encoding'); Chris@17: if ($encoding !== null) { Chris@17: $this->encoding = strtolower($encoding); Chris@17: } Chris@17: Chris@17: $severity = self::getConfigData('severity'); Chris@17: if ($severity !== null) { Chris@17: $this->errorSeverity = (int) $severity; Chris@17: $this->warningSeverity = (int) $severity; Chris@17: } Chris@17: Chris@17: $severity = self::getConfigData('error_severity'); Chris@17: if ($severity !== null) { Chris@17: $this->errorSeverity = (int) $severity; Chris@17: } Chris@17: Chris@17: $severity = self::getConfigData('warning_severity'); Chris@17: if ($severity !== null) { Chris@17: $this->warningSeverity = (int) $severity; Chris@17: } Chris@17: Chris@17: $showWarnings = self::getConfigData('show_warnings'); Chris@17: if ($showWarnings !== null) { Chris@17: $showWarnings = (bool) $showWarnings; Chris@17: if ($showWarnings === false) { Chris@17: $this->warningSeverity = 0; Chris@17: } Chris@17: } Chris@17: Chris@17: $reportWidth = self::getConfigData('report_width'); Chris@17: if ($reportWidth !== null) { Chris@17: $this->reportWidth = $reportWidth; Chris@17: } Chris@17: Chris@17: $showProgress = self::getConfigData('show_progress'); Chris@17: if ($showProgress !== null) { Chris@17: $this->showProgress = (bool) $showProgress; Chris@17: } Chris@17: Chris@17: $quiet = self::getConfigData('quiet'); Chris@17: if ($quiet !== null) { Chris@17: $this->quiet = (bool) $quiet; Chris@17: } Chris@17: Chris@17: $colors = self::getConfigData('colors'); Chris@17: if ($colors !== null) { Chris@17: $this->colors = (bool) $colors; Chris@17: } Chris@17: Chris@17: if (defined('PHP_CODESNIFFER_IN_TESTS') === false) { Chris@17: $cache = self::getConfigData('cache'); Chris@17: if ($cache !== null) { Chris@17: $this->cache = (bool) $cache; Chris@17: } Chris@17: Chris@17: $parallel = self::getConfigData('parallel'); Chris@17: if ($parallel !== null) { Chris@17: $this->parallel = max((int) $parallel, 1); Chris@17: } Chris@17: } Chris@17: Chris@17: }//end restoreDefaults() Chris@17: Chris@17: Chris@17: /** Chris@17: * Processes a short (-e) command line argument. Chris@17: * Chris@17: * @param string $arg The command line argument. Chris@17: * @param int $pos The position of the argument on the command line. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function processShortArgument($arg, $pos) Chris@17: { Chris@17: switch ($arg) { Chris@17: case 'h': Chris@17: case '?': Chris@17: ob_start(); Chris@17: $this->printUsage(); Chris@17: $output = ob_get_contents(); Chris@17: ob_end_clean(); Chris@17: throw new DeepExitException($output, 0); Chris@17: case 'i' : Chris@17: ob_start(); Chris@17: Util\Standards::printInstalledStandards(); Chris@17: $output = ob_get_contents(); Chris@17: ob_end_clean(); Chris@17: throw new DeepExitException($output, 0); Chris@17: case 'v' : Chris@17: if ($this->quiet === true) { Chris@17: // Ignore when quiet mode is enabled. Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->verbosity++; Chris@17: self::$overriddenDefaults['verbosity'] = true; Chris@17: break; Chris@17: case 'l' : Chris@17: $this->local = true; Chris@17: self::$overriddenDefaults['local'] = true; Chris@17: break; Chris@17: case 's' : Chris@17: $this->showSources = true; Chris@17: self::$overriddenDefaults['showSources'] = true; Chris@17: break; Chris@17: case 'a' : Chris@17: $this->interactive = true; Chris@17: self::$overriddenDefaults['interactive'] = true; Chris@17: break; Chris@17: case 'e': Chris@17: $this->explain = true; Chris@17: self::$overriddenDefaults['explain'] = true; Chris@17: break; Chris@17: case 'p' : Chris@17: if ($this->quiet === true) { Chris@17: // Ignore when quiet mode is enabled. Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->showProgress = true; Chris@17: self::$overriddenDefaults['showProgress'] = true; Chris@17: break; Chris@17: case 'q' : Chris@17: // Quiet mode disables a few other settings as well. Chris@17: $this->quiet = true; Chris@17: $this->showProgress = false; Chris@17: $this->verbosity = 0; Chris@17: Chris@17: self::$overriddenDefaults['quiet'] = true; Chris@17: break; Chris@17: case 'm' : Chris@17: $this->recordErrors = false; Chris@17: self::$overriddenDefaults['recordErrors'] = true; Chris@17: break; Chris@17: case 'd' : Chris@17: $ini = explode('=', $this->cliArgs[($pos + 1)]); Chris@17: $this->cliArgs[($pos + 1)] = ''; Chris@17: if (isset($ini[1]) === true) { Chris@17: ini_set($ini[0], $ini[1]); Chris@17: } else { Chris@17: ini_set($ini[0], true); Chris@17: } Chris@17: break; Chris@17: case 'n' : Chris@17: if (isset(self::$overriddenDefaults['warningSeverity']) === false) { Chris@17: $this->warningSeverity = 0; Chris@17: self::$overriddenDefaults['warningSeverity'] = true; Chris@17: } Chris@17: break; Chris@17: case 'w' : Chris@17: if (isset(self::$overriddenDefaults['warningSeverity']) === false) { Chris@17: $this->warningSeverity = $this->errorSeverity; Chris@17: self::$overriddenDefaults['warningSeverity'] = true; Chris@17: } Chris@17: break; Chris@17: default: Chris@17: if ($this->dieOnUnknownArg === false) { Chris@17: $unknown = $this->unknown; Chris@17: $unknown[] = $arg; Chris@17: $this->unknown = $unknown; Chris@17: } else { Chris@17: $this->processUnknownArgument('-'.$arg, $pos); Chris@17: } Chris@17: }//end switch Chris@17: Chris@17: }//end processShortArgument() Chris@17: Chris@17: Chris@17: /** Chris@17: * Processes a long (--example) command line argument. Chris@17: * Chris@17: * @param string $arg The command line argument. Chris@17: * @param int $pos The position of the argument on the command line. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function processLongArgument($arg, $pos) Chris@17: { Chris@17: switch ($arg) { Chris@17: case 'help': Chris@17: ob_start(); Chris@17: $this->printUsage(); Chris@17: $output = ob_get_contents(); Chris@17: ob_end_clean(); Chris@17: throw new DeepExitException($output, 0); Chris@17: case 'version': Chris@17: $output = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') '; Chris@17: $output .= 'by Squiz (http://www.squiz.net)'.PHP_EOL; Chris@17: throw new DeepExitException($output, 0); Chris@17: case 'colors': Chris@17: if (isset(self::$overriddenDefaults['colors']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->colors = true; Chris@17: self::$overriddenDefaults['colors'] = true; Chris@17: break; Chris@17: case 'no-colors': Chris@17: if (isset(self::$overriddenDefaults['colors']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->colors = false; Chris@17: self::$overriddenDefaults['colors'] = true; Chris@17: break; Chris@17: case 'cache': Chris@17: if (isset(self::$overriddenDefaults['cache']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: if (defined('PHP_CODESNIFFER_IN_TESTS') === false) { Chris@17: $this->cache = true; Chris@17: self::$overriddenDefaults['cache'] = true; Chris@17: } Chris@17: break; Chris@17: case 'no-cache': Chris@17: if (isset(self::$overriddenDefaults['cache']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->cache = false; Chris@17: self::$overriddenDefaults['cache'] = true; Chris@17: break; Chris@17: case 'ignore-annotations': Chris@17: if (isset(self::$overriddenDefaults['annotations']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->annotations = false; Chris@17: self::$overriddenDefaults['annotations'] = true; Chris@17: break; Chris@17: case 'config-set': Chris@17: if (isset($this->cliArgs[($pos + 1)]) === false Chris@17: || isset($this->cliArgs[($pos + 2)]) === false Chris@17: ) { Chris@17: $error = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: $key = $this->cliArgs[($pos + 1)]; Chris@17: $value = $this->cliArgs[($pos + 2)]; Chris@17: $current = self::getConfigData($key); Chris@17: Chris@17: try { Chris@17: $this->setConfigData($key, $value); Chris@17: } catch (\Exception $e) { Chris@17: throw new DeepExitException($e->getMessage().PHP_EOL, 3); Chris@17: } Chris@17: Chris@17: $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; Chris@17: Chris@17: if ($current === null) { Chris@17: $output .= "Config value \"$key\" added successfully".PHP_EOL; Chris@17: } else { Chris@17: $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL; Chris@17: } Chris@17: throw new DeepExitException($output, 0); Chris@17: case 'config-delete': Chris@17: if (isset($this->cliArgs[($pos + 1)]) === false) { Chris@17: $error = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; Chris@17: Chris@17: $key = $this->cliArgs[($pos + 1)]; Chris@17: $current = self::getConfigData($key); Chris@17: if ($current === null) { Chris@17: $output .= "Config value \"$key\" has not been set".PHP_EOL; Chris@17: } else { Chris@17: try { Chris@17: $this->setConfigData($key, null); Chris@17: } catch (\Exception $e) { Chris@17: throw new DeepExitException($e->getMessage().PHP_EOL, 3); Chris@17: } Chris@17: Chris@17: $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL; Chris@17: } Chris@17: throw new DeepExitException($output, 0); Chris@17: case 'config-show': Chris@17: ob_start(); Chris@17: $data = self::getAllConfigData(); Chris@17: echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; Chris@17: $this->printConfigData($data); Chris@17: $output = ob_get_contents(); Chris@17: ob_end_clean(); Chris@17: throw new DeepExitException($output, 0); Chris@17: case 'runtime-set': Chris@17: if (isset($this->cliArgs[($pos + 1)]) === false Chris@17: || isset($this->cliArgs[($pos + 2)]) === false Chris@17: ) { Chris@17: $error = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: $key = $this->cliArgs[($pos + 1)]; Chris@17: $value = $this->cliArgs[($pos + 2)]; Chris@17: $this->cliArgs[($pos + 1)] = ''; Chris@17: $this->cliArgs[($pos + 2)] = ''; Chris@17: self::setConfigData($key, $value, true); Chris@17: if (isset(self::$overriddenDefaults['runtime-set']) === false) { Chris@17: self::$overriddenDefaults['runtime-set'] = []; Chris@17: } Chris@17: Chris@17: self::$overriddenDefaults['runtime-set'][$key] = true; Chris@17: break; Chris@17: default: Chris@17: if (substr($arg, 0, 7) === 'sniffs=') { Chris@17: if (isset(self::$overriddenDefaults['sniffs']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $sniffs = explode(',', substr($arg, 7)); Chris@17: foreach ($sniffs as $sniff) { Chris@17: if (substr_count($sniff, '.') !== 2) { Chris@17: $error = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: } Chris@17: Chris@17: $this->sniffs = $sniffs; Chris@17: self::$overriddenDefaults['sniffs'] = true; Chris@17: } else if (substr($arg, 0, 8) === 'exclude=') { Chris@17: if (isset(self::$overriddenDefaults['exclude']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $sniffs = explode(',', substr($arg, 8)); Chris@17: foreach ($sniffs as $sniff) { Chris@17: if (substr_count($sniff, '.') !== 2) { Chris@17: $error = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: } Chris@17: Chris@17: $this->exclude = $sniffs; Chris@17: self::$overriddenDefaults['exclude'] = true; Chris@17: } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false Chris@17: && substr($arg, 0, 6) === 'cache=' Chris@17: ) { Chris@17: if ((isset(self::$overriddenDefaults['cache']) === true Chris@17: && $this->cache === false) Chris@17: || isset(self::$overriddenDefaults['cacheFile']) === true Chris@17: ) { Chris@17: break; Chris@17: } Chris@17: Chris@17: // Turn caching on. Chris@17: $this->cache = true; Chris@17: self::$overriddenDefaults['cache'] = true; Chris@17: Chris@17: $this->cacheFile = Util\Common::realpath(substr($arg, 6)); Chris@17: Chris@17: // It may not exist and return false instead. Chris@17: if ($this->cacheFile === false) { Chris@17: $this->cacheFile = substr($arg, 6); Chris@17: Chris@17: $dir = dirname($this->cacheFile); Chris@17: if (is_dir($dir) === false) { Chris@17: $error = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: if ($dir === '.') { Chris@17: // Passed cache file is a file in the current directory. Chris@17: $this->cacheFile = getcwd().'/'.basename($this->cacheFile); Chris@17: } else { Chris@17: if ($dir{0} === '/') { Chris@17: // An absolute path. Chris@17: $dir = Util\Common::realpath($dir); Chris@17: } else { Chris@17: $dir = Util\Common::realpath(getcwd().'/'.$dir); Chris@17: } Chris@17: Chris@17: if ($dir !== false) { Chris@17: // Cache file path is relative. Chris@17: $this->cacheFile = $dir.'/'.basename($this->cacheFile); Chris@17: } Chris@17: } Chris@17: }//end if Chris@17: Chris@17: self::$overriddenDefaults['cacheFile'] = true; Chris@17: Chris@17: if (is_dir($this->cacheFile) === true) { Chris@17: $error = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: } else if (substr($arg, 0, 10) === 'bootstrap=') { Chris@17: $files = explode(',', substr($arg, 10)); Chris@17: $bootstrap = []; Chris@17: foreach ($files as $file) { Chris@17: $path = Util\Common::realpath($file); Chris@17: if ($path === false) { Chris@17: $error = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: $bootstrap[] = $path; Chris@17: } Chris@17: Chris@17: $this->bootstrap = array_merge($this->bootstrap, $bootstrap); Chris@17: self::$overriddenDefaults['bootstrap'] = true; Chris@17: } else if (substr($arg, 0, 10) === 'file-list=') { Chris@17: $fileList = substr($arg, 10); Chris@17: $path = Util\Common::realpath($fileList); Chris@17: if ($path === false) { Chris@17: $error = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: $files = file($path); Chris@17: foreach ($files as $inputFile) { Chris@17: $inputFile = trim($inputFile); Chris@17: Chris@17: // Skip empty lines. Chris@17: if ($inputFile === '') { Chris@17: continue; Chris@17: } Chris@17: Chris@17: $this->processFilePath($inputFile); Chris@17: } Chris@17: } else if (substr($arg, 0, 11) === 'stdin-path=') { Chris@17: if (isset(self::$overriddenDefaults['stdinPath']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->stdinPath = Util\Common::realpath(substr($arg, 11)); Chris@17: Chris@17: // It may not exist and return false instead, so use whatever they gave us. Chris@17: if ($this->stdinPath === false) { Chris@17: $this->stdinPath = trim(substr($arg, 11)); Chris@17: } Chris@17: Chris@17: self::$overriddenDefaults['stdinPath'] = true; Chris@17: } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') { Chris@17: if (isset(self::$overriddenDefaults['reportFile']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->reportFile = Util\Common::realpath(substr($arg, 12)); Chris@17: Chris@17: // It may not exist and return false instead. Chris@17: if ($this->reportFile === false) { Chris@17: $this->reportFile = substr($arg, 12); Chris@17: Chris@17: $dir = dirname($this->reportFile); Chris@17: if (is_dir($dir) === false) { Chris@17: $error = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: if ($dir === '.') { Chris@17: // Passed report file is a file in the current directory. Chris@17: $this->reportFile = getcwd().'/'.basename($this->reportFile); Chris@17: } else { Chris@17: if ($dir{0} === '/') { Chris@17: // An absolute path. Chris@17: $dir = Util\Common::realpath($dir); Chris@17: } else { Chris@17: $dir = Util\Common::realpath(getcwd().'/'.$dir); Chris@17: } Chris@17: Chris@17: if ($dir !== false) { Chris@17: // Report file path is relative. Chris@17: $this->reportFile = $dir.'/'.basename($this->reportFile); Chris@17: } Chris@17: } Chris@17: }//end if Chris@17: Chris@17: self::$overriddenDefaults['reportFile'] = true; Chris@17: Chris@17: if (is_dir($this->reportFile) === true) { Chris@17: $error = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: } else if (substr($arg, 0, 13) === 'report-width=') { Chris@17: if (isset(self::$overriddenDefaults['reportWidth']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->reportWidth = substr($arg, 13); Chris@17: self::$overriddenDefaults['reportWidth'] = true; Chris@17: } else if (substr($arg, 0, 9) === 'basepath=') { Chris@17: if (isset(self::$overriddenDefaults['basepath']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: self::$overriddenDefaults['basepath'] = true; Chris@17: Chris@17: if (substr($arg, 9) === '') { Chris@17: $this->basepath = null; Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->basepath = Util\Common::realpath(substr($arg, 9)); Chris@17: Chris@17: // It may not exist and return false instead. Chris@17: if ($this->basepath === false) { Chris@17: $this->basepath = substr($arg, 9); Chris@17: } Chris@17: Chris@17: if (is_dir($this->basepath) === false) { Chris@17: $error = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) { Chris@17: $reports = []; Chris@17: Chris@17: if ($arg[6] === '-') { Chris@17: // This is a report with file output. Chris@17: $split = strpos($arg, '='); Chris@17: if ($split === false) { Chris@17: $report = substr($arg, 7); Chris@17: $output = null; Chris@17: } else { Chris@17: $report = substr($arg, 7, ($split - 7)); Chris@17: $output = substr($arg, ($split + 1)); Chris@17: if ($output === false) { Chris@17: $output = null; Chris@17: } else { Chris@17: $dir = dirname($output); Chris@17: if (is_dir($dir) === false) { Chris@17: $error = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: if ($dir === '.') { Chris@17: // Passed report file is a filename in the current directory. Chris@17: $output = getcwd().'/'.basename($output); Chris@17: } else { Chris@17: if ($dir{0} === '/') { Chris@17: // An absolute path. Chris@17: $dir = Util\Common::realpath($dir); Chris@17: } else { Chris@17: $dir = Util\Common::realpath(getcwd().'/'.$dir); Chris@17: } Chris@17: Chris@17: if ($dir !== false) { Chris@17: // Report file path is relative. Chris@17: $output = $dir.'/'.basename($output); Chris@17: } Chris@17: } Chris@17: }//end if Chris@17: }//end if Chris@17: Chris@17: $reports[$report] = $output; Chris@17: } else { Chris@17: // This is a single report. Chris@17: if (isset(self::$overriddenDefaults['reports']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $reportNames = explode(',', substr($arg, 7)); Chris@17: foreach ($reportNames as $report) { Chris@17: $reports[$report] = null; Chris@17: } Chris@17: }//end if Chris@17: Chris@17: // Remove the default value so the CLI value overrides it. Chris@17: if (isset(self::$overriddenDefaults['reports']) === false) { Chris@17: $this->reports = $reports; Chris@17: } else { Chris@17: $this->reports = array_merge($this->reports, $reports); Chris@17: } Chris@17: Chris@17: self::$overriddenDefaults['reports'] = true; Chris@17: } else if (substr($arg, 0, 7) === 'filter=') { Chris@17: if (isset(self::$overriddenDefaults['filter']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->filter = substr($arg, 7); Chris@17: self::$overriddenDefaults['filter'] = true; Chris@17: } else if (substr($arg, 0, 9) === 'standard=') { Chris@17: $standards = trim(substr($arg, 9)); Chris@17: if ($standards !== '') { Chris@17: $this->standards = explode(',', $standards); Chris@17: } Chris@17: Chris@17: self::$overriddenDefaults['standards'] = true; Chris@17: } else if (substr($arg, 0, 11) === 'extensions=') { Chris@17: if (isset(self::$overriddenDefaults['extensions']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $extensions = explode(',', substr($arg, 11)); Chris@17: $newExtensions = []; Chris@17: foreach ($extensions as $ext) { Chris@17: $slash = strpos($ext, '/'); Chris@17: if ($slash !== false) { Chris@17: // They specified the tokenizer too. Chris@17: list($ext, $tokenizer) = explode('/', $ext); Chris@17: $newExtensions[$ext] = strtoupper($tokenizer); Chris@17: continue; Chris@17: } Chris@17: Chris@17: if (isset($this->extensions[$ext]) === true) { Chris@17: $newExtensions[$ext] = $this->extensions[$ext]; Chris@17: } else { Chris@17: $newExtensions[$ext] = 'PHP'; Chris@17: } Chris@17: } Chris@17: Chris@17: $this->extensions = $newExtensions; Chris@17: self::$overriddenDefaults['extensions'] = true; Chris@17: } else if (substr($arg, 0, 7) === 'suffix=') { Chris@17: if (isset(self::$overriddenDefaults['suffix']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->suffix = substr($arg, 7); Chris@17: self::$overriddenDefaults['suffix'] = true; Chris@17: } else if (substr($arg, 0, 9) === 'parallel=') { Chris@17: if (isset(self::$overriddenDefaults['parallel']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->parallel = max((int) substr($arg, 9), 1); Chris@17: self::$overriddenDefaults['parallel'] = true; Chris@17: } else if (substr($arg, 0, 9) === 'severity=') { Chris@17: $this->errorSeverity = (int) substr($arg, 9); Chris@17: $this->warningSeverity = $this->errorSeverity; Chris@17: if (isset(self::$overriddenDefaults['errorSeverity']) === false) { Chris@17: self::$overriddenDefaults['errorSeverity'] = true; Chris@17: } Chris@17: Chris@17: if (isset(self::$overriddenDefaults['warningSeverity']) === false) { Chris@17: self::$overriddenDefaults['warningSeverity'] = true; Chris@17: } Chris@17: } else if (substr($arg, 0, 15) === 'error-severity=') { Chris@17: if (isset(self::$overriddenDefaults['errorSeverity']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->errorSeverity = (int) substr($arg, 15); Chris@17: self::$overriddenDefaults['errorSeverity'] = true; Chris@17: } else if (substr($arg, 0, 17) === 'warning-severity=') { Chris@17: if (isset(self::$overriddenDefaults['warningSeverity']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->warningSeverity = (int) substr($arg, 17); Chris@17: self::$overriddenDefaults['warningSeverity'] = true; Chris@17: } else if (substr($arg, 0, 7) === 'ignore=') { Chris@17: if (isset(self::$overriddenDefaults['ignored']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: // Split the ignore string on commas, unless the comma is escaped Chris@17: // using 1 or 3 slashes (\, or \\\,). Chris@17: $patterns = preg_split( Chris@17: '/(?<=(?ignored = $ignored; Chris@17: self::$overriddenDefaults['ignored'] = true; Chris@17: } else if (substr($arg, 0, 10) === 'generator=' Chris@17: && PHP_CODESNIFFER_CBF === false Chris@17: ) { Chris@17: if (isset(self::$overriddenDefaults['generator']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->generator = substr($arg, 10); Chris@17: self::$overriddenDefaults['generator'] = true; Chris@17: } else if (substr($arg, 0, 9) === 'encoding=') { Chris@17: if (isset(self::$overriddenDefaults['encoding']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->encoding = strtolower(substr($arg, 9)); Chris@17: self::$overriddenDefaults['encoding'] = true; Chris@17: } else if (substr($arg, 0, 10) === 'tab-width=') { Chris@17: if (isset(self::$overriddenDefaults['tabWidth']) === true) { Chris@17: break; Chris@17: } Chris@17: Chris@17: $this->tabWidth = (int) substr($arg, 10); Chris@17: self::$overriddenDefaults['tabWidth'] = true; Chris@17: } else { Chris@17: if ($this->dieOnUnknownArg === false) { Chris@17: $eqPos = strpos($arg, '='); Chris@17: try { Chris@17: if ($eqPos === false) { Chris@17: $this->values[$arg] = $arg; Chris@17: } else { Chris@17: $value = substr($arg, ($eqPos + 1)); Chris@17: $arg = substr($arg, 0, $eqPos); Chris@17: $this->values[$arg] = $value; Chris@17: } Chris@17: } catch (RuntimeException $e) { Chris@17: // Value is not valid, so just ignore it. Chris@17: } Chris@17: } else { Chris@17: $this->processUnknownArgument('--'.$arg, $pos); Chris@17: } Chris@17: }//end if Chris@17: break; Chris@17: }//end switch Chris@17: Chris@17: }//end processLongArgument() Chris@17: Chris@17: Chris@17: /** Chris@17: * Processes an unknown command line argument. Chris@17: * Chris@17: * Assumes all unknown arguments are files and folders to check. Chris@17: * Chris@17: * @param string $arg The command line argument. Chris@17: * @param int $pos The position of the argument on the command line. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function processUnknownArgument($arg, $pos) Chris@17: { Chris@17: // We don't know about any additional switches; just files. Chris@17: if ($arg{0} === '-') { Chris@17: if ($this->dieOnUnknownArg === false) { Chris@17: return; Chris@17: } Chris@17: Chris@17: $error = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: $this->processFilePath($arg); Chris@17: Chris@17: }//end processUnknownArgument() Chris@17: Chris@17: Chris@17: /** Chris@17: * Processes a file path and add it to the file list. Chris@17: * Chris@17: * @param string $path The path to the file to add. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function processFilePath($path) Chris@17: { Chris@17: // If we are processing STDIN, don't record any files to check. Chris@17: if ($this->stdin === true) { Chris@17: return; Chris@17: } Chris@17: Chris@17: $file = Util\Common::realpath($path); Chris@17: if (file_exists($file) === false) { Chris@17: if ($this->dieOnUnknownArg === false) { Chris@17: return; Chris@17: } Chris@17: Chris@17: $error = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL; Chris@17: $error .= $this->printShortUsage(true); Chris@17: throw new DeepExitException($error, 3); Chris@17: } else { Chris@17: // Can't modify the files array directly because it's not a real Chris@17: // class member, so need to use this little get/modify/set trick. Chris@17: $files = $this->files; Chris@17: $files[] = $file; Chris@17: $this->files = $files; Chris@17: self::$overriddenDefaults['files'] = true; Chris@17: } Chris@17: Chris@17: }//end processFilePath() Chris@17: Chris@17: Chris@17: /** Chris@17: * Prints out the usage information for this script. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function printUsage() Chris@17: { Chris@17: echo PHP_EOL; Chris@17: Chris@17: if (PHP_CODESNIFFER_CBF === true) { Chris@17: $this->printPHPCBFUsage(); Chris@17: } else { Chris@17: $this->printPHPCSUsage(); Chris@17: } Chris@17: Chris@17: echo PHP_EOL; Chris@17: Chris@17: }//end printUsage() Chris@17: Chris@17: Chris@17: /** Chris@17: * Prints out the short usage information for this script. Chris@17: * Chris@17: * @param bool $return If TRUE, the usage string is returned Chris@17: * instead of output to screen. Chris@17: * Chris@17: * @return string|void Chris@17: */ Chris@17: public function printShortUsage($return=false) Chris@17: { Chris@17: if (PHP_CODESNIFFER_CBF === true) { Chris@17: $usage = 'Run "phpcbf --help" for usage information'; Chris@17: } else { Chris@17: $usage = 'Run "phpcs --help" for usage information'; Chris@17: } Chris@17: Chris@17: $usage .= PHP_EOL.PHP_EOL; Chris@17: Chris@17: if ($return === true) { Chris@17: return $usage; Chris@17: } Chris@17: Chris@17: echo $usage; Chris@17: Chris@17: }//end printShortUsage() Chris@17: Chris@17: Chris@17: /** Chris@17: * Prints out the usage information for PHPCS. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function printPHPCSUsage() Chris@17: { Chris@17: echo 'Usage: phpcs [-nwlsaepqvi] [-d key[=value]] [--colors] [--no-colors]'.PHP_EOL; Chris@17: echo ' [--cache[=]] [--no-cache] [--tab-width=]'.PHP_EOL; Chris@17: echo ' [--report=] [--report-file=] [--report-=]'.PHP_EOL; Chris@17: echo ' [--report-width=] [--basepath=] [--bootstrap=]'.PHP_EOL; Chris@17: echo ' [--severity=] [--error-severity=] [--warning-severity=]'.PHP_EOL; Chris@17: echo ' [--runtime-set key value] [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL; Chris@17: echo ' [--standard=] [--sniffs=] [--exclude=]'.PHP_EOL; Chris@17: echo ' [--encoding=] [--parallel=] [--generator=]'.PHP_EOL; Chris@17: echo ' [--extensions=] [--ignore=] [--ignore-annotations]'.PHP_EOL; Chris@18: echo ' [--stdin-path=] [--file-list=] [--filter=] - ...'.PHP_EOL; Chris@17: echo PHP_EOL; Chris@17: echo ' - Check STDIN instead of local files and directories'.PHP_EOL; Chris@17: echo ' -n Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL; Chris@17: echo ' -w Print both warnings and errors (this is the default)'.PHP_EOL; Chris@17: echo ' -l Local directory only, no recursion'.PHP_EOL; Chris@17: echo ' -s Show sniff codes in all reports'.PHP_EOL; Chris@17: echo ' -a Run interactively'.PHP_EOL; Chris@17: echo ' -e Explain a standard by showing the sniffs it includes'.PHP_EOL; Chris@17: echo ' -p Show progress of the run'.PHP_EOL; Chris@17: echo ' -q Quiet mode; disables progress and verbose output'.PHP_EOL; Chris@17: echo ' -m Stop error messages from being recorded'.PHP_EOL; Chris@17: echo ' (saves a lot of memory, but stops many reports from being used)'.PHP_EOL; Chris@17: echo ' -v Print processed files'.PHP_EOL; Chris@17: echo ' -vv Print ruleset and token output'.PHP_EOL; Chris@17: echo ' -vvv Print sniff processing information'.PHP_EOL; Chris@17: echo ' -i Show a list of installed coding standards'.PHP_EOL; Chris@17: echo ' -d Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL; Chris@17: echo PHP_EOL; Chris@17: echo ' --help Print this help message'.PHP_EOL; Chris@17: echo ' --version Print version information'.PHP_EOL; Chris@17: echo ' --colors Use colors in output'.PHP_EOL; Chris@17: echo ' --no-colors Do not use colors in output (this is the default)'.PHP_EOL; Chris@17: echo ' --cache Cache results between runs'.PHP_EOL; Chris@17: echo ' --no-cache Do not cache results between runs (this is the default)'.PHP_EOL; Chris@17: echo ' --ignore-annotations Ignore all phpcs: annotations in code comments'.PHP_EOL; Chris@17: echo PHP_EOL; Chris@17: echo ' Use a specific file for caching (uses a temporary file by default)'.PHP_EOL; Chris@17: echo ' A path to strip from the front of file paths inside reports'.PHP_EOL; Chris@17: echo ' A comma separated list of files to run before processing begins'.PHP_EOL; Chris@17: echo ' The encoding of the files being checked (default is utf-8)'.PHP_EOL; Chris@17: echo ' A comma separated list of file extensions to check'.PHP_EOL; Chris@17: echo ' The type of the file can be specified using: ext/type'.PHP_EOL; Chris@17: echo ' e.g., module/php,es/js'.PHP_EOL; Chris@18: echo ' One or more files and/or directories to check'.PHP_EOL; Chris@18: echo ' A file containing a list of files and/or directories to check (one per line)'.PHP_EOL; Chris@18: echo ' Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL; Chris@17: echo ' Uses either the "HTML", "Markdown" or "Text" generator'.PHP_EOL; Chris@17: echo ' (forces documentation generation instead of checking)'.PHP_EOL; Chris@17: echo ' A comma separated list of patterns to ignore files and directories'.PHP_EOL; Chris@17: echo ' How many files should be checked simultaneously (default is 1)'.PHP_EOL; Chris@17: echo ' Print either the "full", "xml", "checkstyle", "csv"'.PHP_EOL; Chris@17: echo ' "json", "junit", "emacs", "source", "summary", "diff"'.PHP_EOL; Chris@18: echo ' "svnblame", "gitblame", "hgblame" or "notifysend" report,'.PHP_EOL; Chris@18: echo ' or specify the path to a custom report class'.PHP_EOL; Chris@17: echo ' (the "full" report is printed by default)'.PHP_EOL; Chris@17: echo ' Write the report to the specified file path'.PHP_EOL; Chris@17: echo ' How many columns wide screen reports should be printed'.PHP_EOL; Chris@17: echo ' or set to "auto" to use current screen width, where supported'.PHP_EOL; Chris@17: echo ' The minimum severity required to display an error or warning'.PHP_EOL; Chris@17: echo ' A comma separated list of sniff codes to include or exclude from checking'.PHP_EOL; Chris@17: echo ' (all sniffs must be part of the specified standard)'.PHP_EOL; Chris@17: echo ' The name or path of the coding standard to use'.PHP_EOL; Chris@17: echo ' If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL; Chris@17: echo ' The number of spaces each tab represents'.PHP_EOL; Chris@17: Chris@17: }//end printPHPCSUsage() Chris@17: Chris@17: Chris@17: /** Chris@17: * Prints out the usage information for PHPCBF. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function printPHPCBFUsage() Chris@17: { Chris@17: echo 'Usage: phpcbf [-nwli] [-d key[=value]] [--ignore-annotations] [--bootstrap=]'.PHP_EOL; Chris@17: echo ' [--standard=] [--sniffs=] [--exclude=] [--suffix=]'.PHP_EOL; Chris@17: echo ' [--severity=] [--error-severity=] [--warning-severity=]'.PHP_EOL; Chris@17: echo ' [--tab-width=] [--encoding=] [--parallel=]'.PHP_EOL; Chris@17: echo ' [--basepath=] [--extensions=] [--ignore=]'.PHP_EOL; Chris@18: echo ' [--stdin-path=] [--file-list=] [--filter=] - ...'.PHP_EOL; Chris@17: echo PHP_EOL; Chris@17: echo ' - Fix STDIN instead of local files and directories'.PHP_EOL; Chris@17: echo ' -n Do not fix warnings (shortcut for --warning-severity=0)'.PHP_EOL; Chris@17: echo ' -w Fix both warnings and errors (on by default)'.PHP_EOL; Chris@17: echo ' -l Local directory only, no recursion'.PHP_EOL; Chris@17: echo ' -p Show progress of the run'.PHP_EOL; Chris@17: echo ' -q Quiet mode; disables progress and verbose output'.PHP_EOL; Chris@17: echo ' -v Print processed files'.PHP_EOL; Chris@17: echo ' -vv Print ruleset and token output'.PHP_EOL; Chris@17: echo ' -vvv Print sniff processing information'.PHP_EOL; Chris@17: echo ' -i Show a list of installed coding standards'.PHP_EOL; Chris@17: echo ' -d Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL; Chris@17: echo PHP_EOL; Chris@17: echo ' --help Print this help message'.PHP_EOL; Chris@17: echo ' --version Print version information'.PHP_EOL; Chris@17: echo ' --ignore-annotations Ignore all phpcs: annotations in code comments'.PHP_EOL; Chris@17: echo PHP_EOL; Chris@17: echo ' A path to strip from the front of file paths inside reports'.PHP_EOL; Chris@17: echo ' A comma separated list of files to run before processing begins'.PHP_EOL; Chris@17: echo ' The encoding of the files being fixed (default is utf-8)'.PHP_EOL; Chris@17: echo ' A comma separated list of file extensions to fix'.PHP_EOL; Chris@17: echo ' The type of the file can be specified using: ext/type'.PHP_EOL; Chris@17: echo ' e.g., module/php,es/js'.PHP_EOL; Chris@18: echo ' One or more files and/or directories to fix'.PHP_EOL; Chris@18: echo ' A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL; Chris@18: echo ' Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL; Chris@17: echo ' A comma separated list of patterns to ignore files and directories'.PHP_EOL; Chris@17: echo ' How many files should be fixed simultaneously (default is 1)'.PHP_EOL; Chris@17: echo ' The minimum severity required to fix an error or warning'.PHP_EOL; Chris@17: echo ' A comma separated list of sniff codes to include or exclude from fixing'.PHP_EOL; Chris@17: echo ' (all sniffs must be part of the specified standard)'.PHP_EOL; Chris@17: echo ' The name or path of the coding standard to use'.PHP_EOL; Chris@17: echo ' If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL; Chris@17: echo ' Write modified files to a filename using this suffix'.PHP_EOL; Chris@17: echo ' ("diff" and "patch" are not used in this mode)'.PHP_EOL; Chris@17: echo ' The number of spaces each tab represents'.PHP_EOL; Chris@17: Chris@17: }//end printPHPCBFUsage() Chris@17: Chris@17: Chris@17: /** Chris@17: * Get a single config value. Chris@17: * Chris@17: * @param string $key The name of the config value. Chris@17: * Chris@17: * @return string|null Chris@17: * @see setConfigData() Chris@17: * @see getAllConfigData() Chris@17: */ Chris@17: public static function getConfigData($key) Chris@17: { Chris@17: $phpCodeSnifferConfig = self::getAllConfigData(); Chris@17: Chris@17: if ($phpCodeSnifferConfig === null) { Chris@17: return null; Chris@17: } Chris@17: Chris@17: if (isset($phpCodeSnifferConfig[$key]) === false) { Chris@17: return null; Chris@17: } Chris@17: Chris@17: return $phpCodeSnifferConfig[$key]; Chris@17: Chris@17: }//end getConfigData() Chris@17: Chris@17: Chris@17: /** Chris@17: * Get the path to an executable utility. Chris@17: * Chris@17: * @param string $name The name of the executable utility. Chris@17: * Chris@17: * @return string|null Chris@17: * @see getConfigData() Chris@17: */ Chris@17: public static function getExecutablePath($name) Chris@17: { Chris@17: $data = self::getConfigData($name.'_path'); Chris@17: if ($data !== null) { Chris@17: return $data; Chris@17: } Chris@17: Chris@17: if ($name === "php") { Chris@17: // For php, we know the executable path. There's no need to look it up. Chris@17: return PHP_BINARY; Chris@17: } Chris@17: Chris@17: if (array_key_exists($name, self::$executablePaths) === true) { Chris@17: return self::$executablePaths[$name]; Chris@17: } Chris@17: Chris@17: if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { Chris@17: $cmd = 'where '.escapeshellarg($name).' 2> nul'; Chris@17: } else { Chris@17: $cmd = 'which '.escapeshellarg($name).' 2> /dev/null'; Chris@17: } Chris@17: Chris@17: $result = exec($cmd, $output, $retVal); Chris@17: if ($retVal !== 0) { Chris@17: $result = null; Chris@17: } Chris@17: Chris@17: self::$executablePaths[$name] = $result; Chris@17: return $result; Chris@17: Chris@17: }//end getExecutablePath() Chris@17: Chris@17: Chris@17: /** Chris@17: * Set a single config value. Chris@17: * Chris@17: * @param string $key The name of the config value. Chris@17: * @param string|null $value The value to set. If null, the config Chris@17: * entry is deleted, reverting it to the Chris@17: * default value. Chris@17: * @param boolean $temp Set this config data temporarily for this Chris@17: * script run. This will not write the config Chris@17: * data to the config file. Chris@17: * Chris@17: * @return bool Chris@17: * @see getConfigData() Chris@18: * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the config file can not be written. Chris@17: */ Chris@17: public static function setConfigData($key, $value, $temp=false) Chris@17: { Chris@17: if (isset(self::$overriddenDefaults['runtime-set']) === true Chris@17: && isset(self::$overriddenDefaults['runtime-set'][$key]) === true Chris@17: ) { Chris@17: return false; Chris@17: } Chris@17: Chris@17: if ($temp === false) { Chris@17: $path = ''; Chris@17: if (is_callable('\Phar::running') === true) { Chris@17: $path = \Phar::running(false); Chris@17: } Chris@17: Chris@17: if ($path !== '') { Chris@17: $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; Chris@17: } else { Chris@17: $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; Chris@17: if (is_file($configFile) === false Chris@17: && strpos('@data_dir@', '@data_dir') === false Chris@17: ) { Chris@17: // If data_dir was replaced, this is a PEAR install and we can Chris@17: // use the PEAR data dir to store the conf file. Chris@17: $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; Chris@17: } Chris@17: } Chris@17: Chris@17: if (is_file($configFile) === true Chris@17: && is_writable($configFile) === false Chris@17: ) { Chris@17: $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL; Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: }//end if Chris@17: Chris@17: $phpCodeSnifferConfig = self::getAllConfigData(); Chris@17: Chris@17: if ($value === null) { Chris@17: if (isset($phpCodeSnifferConfig[$key]) === true) { Chris@17: unset($phpCodeSnifferConfig[$key]); Chris@17: } Chris@17: } else { Chris@17: $phpCodeSnifferConfig[$key] = $value; Chris@17: } Chris@17: Chris@17: if ($temp === false) { Chris@17: $output = '<'.'?php'."\n".' $phpCodeSnifferConfig = '; Chris@17: $output .= var_export($phpCodeSnifferConfig, true); Chris@17: $output .= "\n?".'>'; Chris@17: Chris@17: if (file_put_contents($configFile, $output) === false) { Chris@17: $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL; Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: self::$configDataFile = $configFile; Chris@17: } Chris@17: Chris@17: self::$configData = $phpCodeSnifferConfig; Chris@17: Chris@17: // If the installed paths are being set, make sure all known Chris@17: // standards paths are added to the autoloader. Chris@17: if ($key === 'installed_paths') { Chris@17: $installedStandards = Util\Standards::getInstalledStandardDetails(); Chris@17: foreach ($installedStandards as $name => $details) { Chris@17: Autoload::addSearchPath($details['path'], $details['namespace']); Chris@17: } Chris@17: } Chris@17: Chris@17: return true; Chris@17: Chris@17: }//end setConfigData() Chris@17: Chris@17: Chris@17: /** Chris@17: * Get all config data. Chris@17: * Chris@17: * @return array Chris@17: * @see getConfigData() Chris@17: */ Chris@17: public static function getAllConfigData() Chris@17: { Chris@17: if (self::$configData !== null) { Chris@17: return self::$configData; Chris@17: } Chris@17: Chris@17: $path = ''; Chris@17: if (is_callable('\Phar::running') === true) { Chris@17: $path = \Phar::running(false); Chris@17: } Chris@17: Chris@17: if ($path !== '') { Chris@17: $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; Chris@17: } else { Chris@17: $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; Chris@17: if (is_file($configFile) === false Chris@17: && strpos('@data_dir@', '@data_dir') === false Chris@17: ) { Chris@17: $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; Chris@17: } Chris@17: } Chris@17: Chris@17: if (is_file($configFile) === false) { Chris@17: self::$configData = []; Chris@17: return []; Chris@17: } Chris@17: Chris@17: if (is_readable($configFile) === false) { Chris@17: $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL; Chris@17: throw new DeepExitException($error, 3); Chris@17: } Chris@17: Chris@17: include $configFile; Chris@17: self::$configDataFile = $configFile; Chris@17: self::$configData = $phpCodeSnifferConfig; Chris@17: return self::$configData; Chris@17: Chris@17: }//end getAllConfigData() Chris@17: Chris@17: Chris@17: /** Chris@17: * Prints out the gathered config data. Chris@17: * Chris@17: * @param array $data The config data to print. Chris@17: * Chris@17: * @return void Chris@17: */ Chris@17: public function printConfigData($data) Chris@17: { Chris@17: $max = 0; Chris@17: $keys = array_keys($data); Chris@17: foreach ($keys as $key) { Chris@17: $len = strlen($key); Chris@17: if (strlen($key) > $max) { Chris@17: $max = $len; Chris@17: } Chris@17: } Chris@17: Chris@17: if ($max === 0) { Chris@17: return; Chris@17: } Chris@17: Chris@17: $max += 2; Chris@17: ksort($data); Chris@17: foreach ($data as $name => $value) { Chris@17: echo str_pad($name.': ', $max).$value.PHP_EOL; Chris@17: } Chris@17: Chris@17: }//end printConfigData() Chris@17: Chris@17: Chris@17: }//end class