/** * Get a list of whitelisted file paths. * * @return array */ protected function getWhitelist() { $modified = array(); $cmd = 'git ls-files -o -m --exclude-standard -- ' . $this->basedir; $output = array(); exec($cmd, $output); $basedir = $this->basedir; if (is_dir($basedir) === false) { $basedir = dirname($basedir); } foreach ($output as $path) { $path = Util\Common::realpath($path); do { $modified[$path] = true; $path = dirname($path); } while ($path !== $basedir); } return $modified; }
/** * Check whether the current element of the iterator is acceptable. * * Files are checked for allowed extensions and ignore patterns. * Directories are checked for ignore patterns only. * * @return bool */ public function accept() { $filePath = Util\Common::realpath($this->current()); if ($filePath === false) { return false; } if (is_dir($filePath) === true) { if ($this->config->local === true) { return false; } } else { if ($this->shouldProcessFile($filePath) === false) { return false; } } if ($this->shouldIgnorePath($filePath) === true) { return false; } return true; }
/** * Check whether the current element of the iterator is acceptable. * * If a file is both blacklisted and whitelisted, it will be deemed unacceptable. * * @return bool */ public function accept() { if (parent::accept() === false) { return false; } if ($this->blacklist === null) { $this->blacklist = $this->getblacklist(); } if ($this->whitelist === null) { $this->whitelist = $this->getwhitelist(); } $filePath = Util\Common::realpath($this->current()); // If file is both blacklisted and whitelisted, the blacklist takes precedence. if (isset($this->blacklist[$filePath]) === true) { return false; } if (empty($this->whitelist) === true && empty($this->blacklist) === false) { // We are only checking a blacklist, so everything else should be whitelisted. return true; } return isset($this->whitelist[$filePath]); }
/** * Loads existing cache data for the run, if any. * * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run. * @param \PHP_CodeSniffer\Config $config The config data for the run. * * @return void */ public static function load(Ruleset $ruleset, Config $config) { // Look at every loaded sniff class so far and use their file contents // to generate a hash for the code used during the run. // At this point, the loaded class list contains the core PHPCS code // and all sniffs that have been loaded as part of the run. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo PHP_EOL . "\tGenerating loaded file list for code hash" . PHP_EOL; } $codeHash = ''; $classes = array_keys(Autoload::getLoadedClasses()); sort($classes); $installDir = dirname(__DIR__); $installDirLen = strlen($installDir); $standardDir = $installDir . DIRECTORY_SEPARATOR . 'Standards'; $standardDirLen = strlen($standardDir); foreach ($classes as $file) { if (substr($file, 0, $standardDirLen) !== $standardDir) { if (substr($file, 0, $installDirLen) === $installDir) { // We are only interested in sniffs here. continue; } if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t\t=> external file: {$file}" . PHP_EOL; } } else { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t\t=> internal sniff: {$file}" . PHP_EOL; } } $codeHash .= md5_file($file); } // Add the content of the used rulesets to the hash so that sniff setting // changes in the ruleset invalidate the cache. $rulesets = $ruleset->paths; sort($rulesets); foreach ($rulesets as $file) { if (substr($file, 0, $standardDirLen) !== $standardDir) { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t\t=> external ruleset: {$file}" . PHP_EOL; } } else { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t\t=> internal ruleset: {$file}" . PHP_EOL; } } $codeHash .= md5_file($file); } // Go through the core PHPCS code and add those files to the file // hash. This ensures that core PHPCS changes will also invalidate the cache. // Note that we ignore sniffs here, and any files that don't affect // the outcome of the run. $di = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($installDir), 0, \RecursiveIteratorIterator::CATCH_GET_CHILD); $di = new \RecursiveDirectoryIterator($installDir); $filter = new \RecursiveCallbackFilterIterator($di, function ($file, $key, $iterator) { // Skip hidden files. $filename = $file->getFilename(); if (substr($filename, 0, 1) === '.') { return false; } $filePath = Common::realpath($file->getPathname()); if ($filePath === false) { return false; } if (is_dir($filePath) === true && ($filename === 'Standards' || $filename === 'Exceptions' || $filename === 'Reports' || $filename === 'Generators')) { return false; } return true; }); $iterator = new \RecursiveIteratorIterator($filter); foreach ($iterator as $file) { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t\t=> core file: {$file}" . PHP_EOL; } $codeHash .= md5_file($file); } $codeHash = md5($codeHash); // Along with the code hash, use various settings that can affect // the results of a run to create a new hash. This hash will be used // in the cache file name. $rulesetHash = md5(var_export($ruleset->ignorePatterns, true) . var_export($ruleset->includePatterns, true)); $configData = array('tabWidth' => $config->tabWidth, 'encoding' => $config->encoding, 'recordErrors' => $config->recordErrors, 'codeHash' => $codeHash, 'rulesetHash' => $rulesetHash); $configString = implode(',', $configData); $cacheHash = substr(sha1($configString), 0, 12); if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\tGenerating cache key data" . PHP_EOL; echo "\t\t=> tabWidth: " . $configData['tabWidth'] . PHP_EOL; echo "\t\t=> encoding: " . $configData['encoding'] . PHP_EOL; echo "\t\t=> recordErrors: " . (int) $configData['recordErrors'] . PHP_EOL; echo "\t\t=> codeHash: " . $configData['codeHash'] . PHP_EOL; echo "\t\t=> rulesetHash: " . $configData['rulesetHash'] . PHP_EOL; echo "\t\t=> cacheHash: {$cacheHash}" . PHP_EOL; } if ($config->cacheFile !== null) { $cacheFile = $config->cacheFile; } else { // Determine the common paths for all files being checked. // We can use this to locate an existing cache file, or to // determine where to create a new one. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\tChecking possible cache file paths" . PHP_EOL; } $paths = array(); foreach ($config->files as $file) { $file = Common::realpath($file); while ($file !== DIRECTORY_SEPARATOR) { if (isset($paths[$file]) === false) { $paths[$file] = 1; } else { $paths[$file]++; } $lastFile = $file; $file = dirname($file); if ($file === $lastFile) { // Just in case something went wrong, // we don't want to end up in an infinite loop. break; } } } ksort($paths); $paths = array_reverse($paths); $numFiles = count($config->files); $tmpDir = sys_get_temp_dir(); $cacheFile = null; foreach ($paths as $file => $count) { if ($count !== $numFiles) { unset($paths[$file]); continue; } $fileHash = substr(sha1($file), 0, 12); $testFile = $tmpDir . DIRECTORY_SEPARATOR . "phpcs.{$fileHash}.{$cacheHash}.cache"; if ($cacheFile === null) { // This will be our default location if we can't find // an existing file. $cacheFile = $testFile; } if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t\t=> {$testFile}" . PHP_EOL; echo "\t\t\t * based on shared location: {$file} *" . PHP_EOL; } if (file_exists($testFile) === true) { $cacheFile = $testFile; break; } } //end foreach if ($cacheFile === null) { // Unlikely, but just in case $paths is empty for some reason. $cacheFile = $tmpDir . DIRECTORY_SEPARATOR . "phpcs.{$cacheHash}.cache"; } } //end if self::$path = $cacheFile; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t=> Using cache file: " . self::$path . PHP_EOL; } if (file_exists(self::$path) === true) { self::$cache = json_decode(file_get_contents(self::$path), true); // Verify the contents of the cache file. if (self::$cache['config'] !== $configData) { self::$cache = array(); if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t* cache was invalid and has been cleared *" . PHP_EOL; } } } else { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t* cache file does not exist *" . PHP_EOL; } } self::$cache['config'] = $configData; }
/** * Expands a ruleset reference into a list of sniff files. * * @param string $ref The reference from the ruleset XML file. * @param string $rulesetDir The directory of the ruleset XML file, used to * evaluate relative paths. * @param int $depth How many nested processing steps we are in. This * is only used for debug output. * * @return array * @throws RuntimeException If the reference is invalid. */ private function expandRulesetReference($ref, $rulesetDir, $depth = 0) { // Ignore internal sniffs codes as they are used to only // hide and change internal messages. if (substr($ref, 0, 9) === 'Internal.') { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* ignoring internal sniff code *" . PHP_EOL; } return array(); } // As sniffs can't begin with a full stop, assume references in // this format are relative paths and attempt to convert them // to absolute paths. If this fails, let the reference run through // the normal checks and have it fail as normal. if (substr($ref, 0, 1) === '.') { $realpath = Util\Common::realpath($rulesetDir . '/' . $ref); if ($realpath !== false) { $ref = $realpath; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } } // As sniffs can't begin with a tilde, assume references in // this format are relative to the user's home directory. if (substr($ref, 0, 2) === '~/') { $realpath = Util\Common::realpath($ref); if ($realpath !== false) { $ref = $realpath; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } } if (is_file($ref) === true) { if (substr($ref, -9) === 'Sniff.php') { // A single external sniff. $this->rulesetDirs[] = dirname(dirname(dirname($ref))); return array($ref); } } else { // See if this is a whole standard being referenced. $path = Util\Standards::getInstalledStandardPath($ref); if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { // If the ruleset exists inside the phar file, use it. if (file_exists($path . DIRECTORY_SEPARATOR . 'ruleset.xml') === true) { $path = $path . DIRECTORY_SEPARATOR . 'ruleset.xml'; } else { $path = null; } } if ($path !== null) { $ref = $path; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } else { if (is_dir($ref) === false) { // Work out the sniff path. $sepPos = strpos($ref, DIRECTORY_SEPARATOR); if ($sepPos !== false) { $stdName = substr($ref, 0, $sepPos); $path = substr($ref, $sepPos); } else { $parts = explode('.', $ref); $stdName = $parts[0]; if (count($parts) === 1) { // A whole standard? $path = ''; } else { if (count($parts) === 2) { // A directory of sniffs? $path = DIRECTORY_SEPARATOR . 'Sniffs' . DIRECTORY_SEPARATOR . $parts[1]; } else { // A single sniff? $path = DIRECTORY_SEPARATOR . 'Sniffs' . DIRECTORY_SEPARATOR . $parts[1] . DIRECTORY_SEPARATOR . $parts[2] . 'Sniff.php'; } } } $newRef = false; $stdPath = Util\Standards::getInstalledStandardPath($stdName); if ($stdPath !== null && $path !== '') { if (Util\Common::isPharFile($stdPath) === true && strpos($stdPath, 'ruleset.xml') === false) { // Phar files can only return the directory, // since ruleset can be omitted if building one standard. $newRef = Util\Common::realpath($stdPath . $path); } else { $newRef = Util\Common::realpath(dirname($stdPath) . $path); } } if ($newRef === false) { // The sniff is not locally installed, so check if it is being // referenced as a remote sniff outside the install. We do this // by looking through all directories where we have found ruleset // files before, looking for ones for this particular standard, // and seeing if it is in there. foreach ($this->rulesetDirs as $dir) { if (strtolower(basename($dir)) !== strtolower($stdName)) { continue; } $newRef = Util\Common::realpath($dir . $path); if ($newRef !== false) { $ref = $newRef; } } } else { $ref = $newRef; } if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> " . Util\Common::stripBasepath($ref, $this->config->basepath) . PHP_EOL; } } } //end if } //end if if (is_dir($ref) === true) { if (is_file($ref . DIRECTORY_SEPARATOR . 'ruleset.xml') === true) { // We are referencing an external coding standard. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* rule is referencing a standard using directory name; processing *" . PHP_EOL; } return $this->processRuleset($ref . DIRECTORY_SEPARATOR . 'ruleset.xml', $depth + 2); } else { // We are referencing a whole directory of sniffs. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* rule is referencing a directory of sniffs *" . PHP_EOL; echo str_repeat("\t", $depth); echo "\t\tAdding sniff files from directory" . PHP_EOL; } return $this->expandSniffDirectory($ref, $depth + 1); } } else { if (is_file($ref) === false) { $error = "Referenced sniff \"{$ref}\" does not exist"; throw new RuntimeException($error); } if (substr($ref, -9) === 'Sniff.php') { // A single sniff. return array($ref); } else { // Assume an external ruleset.xml file. if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* rule is referencing a standard using ruleset path; processing *" . PHP_EOL; } return $this->processRuleset($ref, $depth + 2); } } //end if }
/** * Return the path of an installed coding standard. * * Coding standards are directories located in the * CodeSniffer/Standards directory. Valid coding standards * include a ruleset.xml file. * * @param string $standard The name of the coding standard. * * @return string|null */ public static function getInstalledStandardPath($standard) { $installedPaths = self::getInstalledStandardPaths(); foreach ($installedPaths as $installedPath) { if (basename($installedPath) === $standard) { $standardPath = $installedPath; } else { $standardPath = $installedPath . DIRECTORY_SEPARATOR . $standard; } $path = Common::realpath($standardPath . DIRECTORY_SEPARATOR . 'ruleset.xml'); if (is_file($path) === true) { return $path; } else { if (Common::isPharFile($standardPath) === true) { $path = Common::realpath($standardPath); if ($path !== false) { return $path; } } } } return null; }
/** * Processes a file path and add it to the file list. * * @param string $path The path to the file to add. * * @return void */ 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; } echo 'ERROR: The file "' . $path . '" does not exist.' . PHP_EOL . PHP_EOL; $this->printUsage(); exit(3); } else { $files = $this->files; $files[] = $file; $this->files = $files; $this->overriddenDefaults['files'] = true; } }