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