Skip to content
Browse files

Finished adding support for directories of sniffs, --sniffs command l…

…ine arg, and specifying multiple standards on the command line
  • Loading branch information...
1 parent 59ef86c commit bd7033edf17a84fe8ff03fb12f72fd4fca18ea85 @gsherwood gsherwood committed Mar 27, 2013
Showing with 237 additions and 192 deletions.
  1. +197 −163 CodeSniffer.php
  2. +26 −16 CodeSniffer/CLI.php
  3. +2 −2 CodeSniffer/Reporting.php
  4. +7 −2 package.xml
  5. +4 −8 tests/Core/ErrorSuppressionTest.php
  6. +1 −1 tests/Standards/AbstractSniffUnitTest.php
View
360 CodeSniffer.php
@@ -224,14 +224,6 @@ public function __construct(
define('PHPCS_DEFAULT_WARN_SEV', 5);
}
- // Change into a directory that we know about to stop any
- // relative path conflicts.
- if (defined('PHPCS_CWD') === false) {
- define('PHPCS_CWD', getcwd());
- }
-
-#chdir(dirname(__FILE__).'/CodeSniffer/');
-
// Set default CLI object in case someone is running us
// without using the command line script.
$this->cli = new PHP_CodeSniffer_CLI();
@@ -245,21 +237,6 @@ public function __construct(
/**
- * Destructs a PHP_CodeSniffer object.
- *
- * Restores the current working directory to what it
- * was before we started our run.
- *
- * @return void
- */
- public function __destruct()
- {
- chdir(PHPCS_CWD);
-
- }//end __destruct()
-
-
- /**
* Autoload static method for loading classes and interfaces.
*
* @param string $className The name of the class or interface.
@@ -387,61 +364,59 @@ public function setCli($cli)
/**
* Processes the files/directories that PHP_CodeSniffer was constructed with.
*
- * @param string|array $files The files and directories to process. For
- * directories, each sub directory will also
- * be traversed for source files.
- * @param string $standard The set of code sniffs we are testing
- * against.
- * @param array $sniffs The sniff names to restrict the allowed
- * listeners to.
- * @param boolean $local If true, don't recurse into directories.
+ * @param string|array $files The files and directories to process. For
+ * directories, each sub directory will also
+ * be traversed for source files.
+ * @param string|array $standards The set of code sniffs we are testing
+ * against.
+ * @param array $restrictions The sniff names to restrict the allowed
+ * listeners to.
+ * @param boolean $local If true, don't recurse into directories.
*
* @return void
* @throws PHP_CodeSniffer_Exception If files or standard are invalid.
*/
- public function process($files, $standard, array $sniffs=array(), $local=false)
+ public function process($files, $standards, array $restrictions=array(), $local=false)
{
if (is_array($files) === false) {
- if (is_string($files) === false || $files === null) {
- throw new PHP_CodeSniffer_Exception('$file must be a string');
- }
-
$files = array($files);
}
- if (is_string($standard) === false || $standard === null) {
- throw new PHP_CodeSniffer_Exception('$standard must be a string');
+ if (is_array($standards) === false) {
+ $standards = array($standards);
}
// Reset the members.
$this->listeners = array();
$this->sniffs = array();
$this->ruleset = array();
$this->_tokenListeners = array();
+ self::$rulesetDirs = array();
// Ensure this option is enabled or else line endings will not always
// be detected properly for files created on a Mac with the /r line ending.
ini_set('auto_detect_line_endings', true);
- $path = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$standard.'/ruleset.xml');
- if (is_file($path) === true) {
- $standard = $path;
- }
+ $sniffs = array();
+ foreach ($standards as $standard) {
+ $standard = $this->getInstalledStandardPath($standard);
- if (PHP_CODESNIFFER_VERBOSITY === 1) {
- $ruleset = simplexml_load_file($standard);
- if ($ruleset !== false) {
- $standardName = (string) $ruleset['name'];
- }
+ if (PHP_CODESNIFFER_VERBOSITY === 1) {
+ $ruleset = simplexml_load_file($standard);
+ if ($ruleset !== false) {
+ $standardName = (string) $ruleset['name'];
+ }
- echo "Registering sniffs in the $standardName standard... ";
- if (PHP_CODESNIFFER_VERBOSITY > 2) {
- echo PHP_EOL;
+ echo "Registering sniffs in the $standardName standard... ";
+ if (count($standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) {
+ echo PHP_EOL;
+ }
}
- }
- $sniffs = $this->processRuleset($standard);
- $this->registerSniffs($sniffs);
+ $sniffs = array_merge($sniffs, $this->processRuleset($standard));
+ }//end foreach
+
+ $this->registerSniffs($sniffs, $restrictions);
$this->populateTokenListeners();
if (PHP_CODESNIFFER_VERBOSITY === 1) {
@@ -532,11 +507,19 @@ public function process($files, $standard, array $sniffs=array(), $local=false)
}//end process()
-
-
-
-
-
+ /**
+ * Processes a single ruleset and returns a list of the sniffs it represents.
+ *
+ * Rules founds within the ruleset are processed immediately, but sniff classes
+ * are not registered by this method.
+ *
+ * @param string $rulesetPath The path to a ruleset XML file.
+ * @param int $depth How many nested processing steps we are in. This
+ * is only used for debug output.
+ *
+ * @return array
+ * @throws PHP_CodeSniffer_Exception If the ruleset path is invalid.
+ */
public function processRuleset($rulesetPath, $depth=0)
{
$rulesetPath = realpath($rulesetPath);
@@ -555,6 +538,16 @@ public function processRuleset($rulesetPath, $depth=0)
$excludedSniffs = array();
$rulesetDir = dirname($rulesetPath);
+ if (in_array($rulesetDir, self::$rulesetDirs) === true) {
+ // We've already processed this ruleset.
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ echo str_repeat("\t", $depth);
+ echo "\t* ruleset already processed; skipping *".PHP_EOL;
+ }
+
+ return array();
+ }
+
self::$rulesetDirs[] = $rulesetDir;
if (is_dir($rulesetDir.'/Sniffs') === true) {
@@ -563,35 +556,8 @@ public function processRuleset($rulesetPath, $depth=0)
echo "\tAdding sniff files from \".../".basename($rulesetDir)."/Sniffs/\" directory".PHP_EOL;
}
- $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($rulesetDir.'/Sniffs'));
- foreach ($di as $file) {
- $fileName = $file->getFilename();
-
- // Skip hidden files.
- if (substr($fileName, 0, 1) === '.') {
- continue;
- }
-
- // We are only interested in PHP and sniff files.
- $fileParts = explode('.', $fileName);
- if (array_pop($fileParts) !== 'php') {
- continue;
- }
-
- $basename = basename($fileName, '.php');
- if (substr($basename, -5) !== 'Sniff') {
- continue;
- }
-
- $path = $file->getPathname();
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- echo str_repeat("\t", $depth);
- echo "\t\t=> $path".PHP_EOL;
- }
-
- $ownSniffs[] = $path;
- }//end foreach
- }//end if
+ $ownSniffs = $this->_expandSniffDirectory($rulesetDir.'/Sniffs', $depth);
+ }
foreach ($ruleset->rule as $rule) {
if (isset($rule['ref']) === false) {
@@ -622,7 +588,7 @@ public function processRuleset($rulesetPath, $depth=0)
}
}
- $this->populateCustomRules($rule, $depth);
+ $this->_processRule($rule, $depth);
}//end foreach
// Process custom ignore pattern rules.
@@ -664,19 +630,64 @@ public function processRuleset($rulesetPath, $depth=0)
}//end processRuleset()
+ /**
+ * Expands a directory into a list of sniff files within.
+ *
+ * @param string $directory The path to a directory.
+ * @param int $depth How many nested processing steps we are in. This
+ * is only used for debug output.
+ *
+ * @return array
+ */
+ private function _expandSniffDirectory($directory, $depth=0)
+ {
+ $sniffs = array();
+ $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
+ foreach ($di as $file) {
+ $fileName = $file->getFilename();
+ // Skip hidden files.
+ if (substr($fileName, 0, 1) === '.') {
+ continue;
+ }
+ // We are only interested in PHP and sniff files.
+ $fileParts = explode('.', $fileName);
+ if (array_pop($fileParts) !== 'php') {
+ continue;
+ }
+
+ $basename = basename($fileName, '.php');
+ if (substr($basename, -5) !== 'Sniff') {
+ continue;
+ }
+
+ $path = $file->getPathname();
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ echo str_repeat("\t", $depth);
+ echo "\t\t=> $path".PHP_EOL;
+ }
+
+ $sniffs[] = $path;
+ }//end foreach
+
+ return $sniffs;
+
+ }//end _expandSniffDirectory()
/**
- * Expand a ruleset sniff reference into a list of sniff files.
+ * Expands a ruleset reference into a list of sniff files.
*
- * @param string $sniff The sniff reference from the ruleset.xml file.
- * @param string $rulesetDir The directory of the ruleset.xml file.
+ * @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 PHP_CodeSniffer_Exception If the sniff reference is invalid.
+ * @throws PHP_CodeSniffer_Exception If the reference is invalid.
*/
private function _expandRulesetReference($ref, $rulesetDir, $depth=0)
{
@@ -754,9 +765,11 @@ private function _expandRulesetReference($ref, $rulesetDir, $depth=0)
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;
}
-//$referencedSniffs = $this->getSniffFiles($path);
+ return $this->_expandSniffDirectory($ref, ($depth + 1));
}
} else {
if (is_file($ref) === false) {
@@ -778,80 +791,19 @@ private function _expandRulesetReference($ref, $rulesetDir, $depth=0)
}
}//end if
- return array();
-
}//end _expandRulesetReference()
/**
- * Sets installed sniffs in the coding standard being used.
- *
- * @param string $standard The name of the coding standard we are checking.
- * Can also be a path to a custom ruleset.xml file.
- *
- * @return void
- */
- public function registerSniffs($files)
- {
- $listeners = array();
-
- foreach ($files as $file) {
- // Work out where the position of /StandardName/Sniffs/... is
- // so we can determine what the class will be called.
- $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
- if ($sniffPos === false) {
- continue;
- }
-
- $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
- if ($slashPos === false) {
- continue;
- }
-
- $className = substr($file, ($slashPos + 1));
- $className = substr($className, 0, -4);
- $className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
-
- include_once $file;
-
- // Support the use of PHP namespaces. If the class name we included
- // contains namespace separators instead of underscores, use this as the
- // class name from now on.
- $classNameNS = str_replace('_', '\\', $className);
- if (class_exists($classNameNS, false) === true) {
- $className = $classNameNS;
- }
-
-// If they have specified a list of sniffs to restrict to, check
-// to see if this sniff is allowed.
-#$allowed = in_array(strtolower($className), $sniffs);
-#if (empty($sniffs) === false && $allowed === false) {
-# continue;
-#}
-
- $listeners[$className] = $className;
-
- if (PHP_CODESNIFFER_VERBOSITY > 2) {
- echo "Registered $className".PHP_EOL;
- }
- }//end foreach
-
- $this->sniffs = $listeners;
-
- }//end registerSniffs()
-
-
-
-
- /**
- * Sets installed sniffs in the coding standard being used.
+ * Processes a rule from a ruleset XML file, overriding built-in defaults.
*
- * @param string $standard The name of the coding standard we are checking.
- * Can also be a path to a custom ruleset.xml file.
+ * @param SimpleXMLElement $rule The rule object from a ruleset XML file.
+ * @param int $depth How many nested processing steps we are in.
+ * This is only used for debug output.
*
* @return void
*/
- public function populateCustomRules($rule, $depth=0)
+ private function _processRule($rule, $depth=0)
{
$code = (string) $rule['ref'];
@@ -942,7 +894,67 @@ public function populateCustomRules($rule, $depth=0)
}
}
- }//end populateCustomRules()
+ }//end _processRule()
+
+
+ /**
+ * Loads and stores sniffs objects used for sniffing files.
+ *
+ * @param array $files Paths to the sniff files to register.
+ * @param array $restrictions The sniff class names to restrict the allowed
+ * listeners to.
+ *
+ * @return void
+ */
+ public function registerSniffs($files, $restrictions)
+ {
+ $listeners = array();
+
+ foreach ($files as $file) {
+ // Work out where the position of /StandardName/Sniffs/... is
+ // so we can determine what the class will be called.
+ $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
+ if ($sniffPos === false) {
+ continue;
+ }
+
+ $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
+ if ($slashPos === false) {
+ continue;
+ }
+
+ $className = substr($file, ($slashPos + 1));
+ $className = substr($className, 0, -4);
+ $className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
+
+ include_once $file;
+
+ // Support the use of PHP namespaces. If the class name we included
+ // contains namespace separators instead of underscores, use this as the
+ // class name from now on.
+ $classNameNS = str_replace('_', '\\', $className);
+ if (class_exists($classNameNS, false) === true) {
+ $className = $classNameNS;
+ }
+
+ // If they have specified a list of sniffs to restrict to, check
+ // to see if this sniff is allowed.
+ if (empty($restrictions) === false
+ && in_array(strtolower($className), $restrictions) === false
+ ) {
+ continue;
+ }
+
+ $listeners[$className] = $className;
+
+ if (PHP_CODESNIFFER_VERBOSITY > 2) {
+ echo "Registered $className".PHP_EOL;
+ }
+ }//end foreach
+
+ $this->sniffs = $listeners;
+
+ }//end registerSniffs()
/**
@@ -1888,9 +1900,8 @@ public static function getInstalledStandards(
*/
public static function isInstalledStandard($standard)
{
- $standardDir = dirname(__FILE__);
- $standardDir .= '/CodeSniffer/Standards/'.$standard;
- if (is_file($standardDir.'/ruleset.xml') === true) {
+ $path = self::getInstalledStandardPath($standard);
+ if ($path !== null) {
return true;
} else {
// This could be a custom standard, installed outside our
@@ -1915,6 +1926,29 @@ public static function isInstalledStandard($standard)
/**
+ * Return the path of an installed coding standard.
+ *
+ * Coding standards are directories located in the
+ * CodeSniffer/Standards directory. Valid coding standards
+ * include a Sniffs subdirectory.
+ *
+ * @param string $standard The name of the coding standard.
+ *
+ * @return string|null
+ */
+ public static function getInstalledStandardPath($standard)
+ {
+ $path = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$standard.'/ruleset.xml');
+ if (is_file($path) === true) {
+ return $path;
+ }
+
+ return null;
+
+ }//end getInstalledStandardPath()
+
+
+ /**
* Get a single config value.
*
* Config data is stored in the data dir, in a file called
View
42 CodeSniffer/CLI.php
@@ -336,8 +336,8 @@ public function processLongArgument($arg, $pos, $values)
// Convert the sniffs to class names.
foreach ($sniffs as $sniff) {
- $parts = explode('.', $sniff);
- $values['sniffs'][] = $parts[0].'_Sniffs_'.$parts[1].'_'.$parts[2].'Sniff';
+ $parts = explode('.', strtolower($sniff));
+ $values['sniffs'][] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff';
}
} else if (substr($arg, 0, 12) === 'report-file=') {
$values['reportFile'] = realpath(substr($arg, 12));
@@ -427,7 +427,7 @@ public function processLongArgument($arg, $pos, $values)
$values['reports'][$report] = $output;
} else if (substr($arg, 0, 9) === 'standard=') {
- $values['standard'] = substr($arg, 9);
+ $values['standard'] = explode(',', substr($arg, 9));
} else if (substr($arg, 0, 11) === 'extensions=') {
$values['extensions'] = explode(',', substr($arg, 11));
} else if (substr($arg, 0, 9) === 'severity=') {
@@ -524,25 +524,35 @@ public function process($values=array())
if ($values['generator'] !== '') {
$phpcs = new PHP_CodeSniffer($values['verbosity']);
- $phpcs->generateDocs(
- $values['standard'],
- $values['files'],
- $values['generator']
- );
+ foreach ($values['standard'] as $standard) {
+ $phpcs->generateDocs(
+ $standard,
+ $values['files'],
+ $values['generator']
+ );
+ }
exit(0);
}
- $values['standard'] = $this->validateStandard($values['standard']);
- if (PHP_CodeSniffer::isInstalledStandard($values['standard']) === false) {
- // They didn't select a valid coding standard, so help them
- // out by letting them know which standards are installed.
- echo 'ERROR: the "'.$values['standard'].'" coding standard is not installed. ';
- $this->printInstalledStandards();
- exit(2);
+ // If no standard is supplied, get the default.
+ if ($values['standard'] === null) {
+ $values['standard'] = $this->validateStandard(null);
+ } else {
+ foreach ($values['standard'] as $standard) {
+ if (PHP_CodeSniffer::isInstalledStandard($standard) === false) {
+ // They didn't select a valid coding standard, so help them
+ // out by letting them know which standards are installed.
+ echo 'ERROR: the "'.$standard.'" coding standard is not installed. ';
+ $this->printInstalledStandards();
+ exit(2);
+ }
+ }
}
if ($values['explain'] === true) {
- $this->explainStandard($values['standard']);
+ foreach ($values['standard'] as $standard) {
+ $this->explainStandard($standard);
+ }
exit(0);
}
View
4 CodeSniffer/Reporting.php
@@ -137,7 +137,7 @@ public function cacheFileReport(PHP_CodeSniffer_File $phpcsFile, array $cliValue
}
if ($output === null) {
- $output = PHPCS_CWD.'/phpcs-'.$report.'.tmp';
+ $output = getcwd().'/phpcs-'.$report.'.tmp';
}
file_put_contents($output, $generatedReport, $flags);
@@ -181,7 +181,7 @@ public function printReport(
$toScreen = false;
ob_start();
} else {
- $filename = PHPCS_CWD.'/phpcs-'.$report.'.tmp';
+ $filename = getcwd().'/phpcs-'.$report.'.tmp';
$toScreen = true;
}
View
9 package.xml
@@ -19,15 +19,20 @@ http://pear.php.net/dtd/package-2.0.xsd">
<version>
<release>1.5.0RC2</release>
<api>1.5.0RC2</api>
- <release>1.4.5</release>
- <api>1.4.5</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license uri="https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt">BSD 3-Clause License</license>
<notes>
+ - Ruleset processing has been rewritten to be more predictable
+ -- Provides much better support for relative paths inside ruleset files
+ -- May mean that sniffs that were previously ignored are now being included when importing external rulesets
+ -- Ruleset processing output can be seen by using the -vv command line argument
+ -- Internal sniff registering functions have all changed, so please review custom scripts
+ - You can now pass multiple coding standards on the command line, comma separated
+ -- Works with built-in or custom standards and rulesets, or a mix of both
- Added Generic LowerCaseKeywordSniff to ensure all PHP keywords are defined in lowercase
-- The PSR2 and Squiz standards now use this sniff
- Added Generic SAPIUsageSniff to ensure the PHP_SAPI constant is used instead of php_sapi_name() (request #19863)
View
12 tests/Core/ErrorSuppressionTest.php
@@ -37,8 +37,7 @@ class Core_ErrorSuppressionTest extends PHPUnit_Framework_TestCase
public function testSuppressError()
{
$phpcs = new PHP_CodeSniffer();
- $phpcs->setTokenListeners('PEAR', array('Generic_Sniffs_PHP_LowerCaseConstantSniff'));
- $phpcs->populateTokenListeners();
+ $phpcs->process(array(), 'PEAR', array('generic_sniffs_php_lowercaseconstantsniff'));
// Process without suppression.
$content = '<?php '.PHP_EOL.'$var = FALSE;';
@@ -69,8 +68,7 @@ public function testSuppressError()
public function testSuppressSomeErrors()
{
$phpcs = new PHP_CodeSniffer();
- $phpcs->setTokenListeners('PEAR', array('Generic_Sniffs_PHP_LowerCaseConstantSniff'));
- $phpcs->populateTokenListeners();
+ $phpcs->process(array(), 'Generic', array('generic_sniffs_php_lowercaseconstantsniff'));
// Process without suppression.
$content = '<?php '.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = TRUE;';
@@ -101,8 +99,7 @@ public function testSuppressSomeErrors()
public function testSuppressWarning()
{
$phpcs = new PHP_CodeSniffer();
- $phpcs->setTokenListeners('Squiz', array('Generic_Sniffs_Commenting_TodoSniff'));
- $phpcs->populateTokenListeners();
+ $phpcs->process(array(), 'Generic', array('generic_sniffs_commenting_todosniff'));
// Process without suppression.
$content = '<?php '.PHP_EOL.'//TODO: write some code';
@@ -133,8 +130,7 @@ public function testSuppressWarning()
public function testSuppressFile()
{
$phpcs = new PHP_CodeSniffer();
- $phpcs->setTokenListeners('Squiz', array('Squiz_Sniffs_Commenting_FileCommentSniff'));
- $phpcs->populateTokenListeners();
+ $phpcs->process(array(), 'Squiz', array('squiz_sniffs_commenting_filecommentsniff'));
// Process without suppression.
$content = '<?php '.PHP_EOL.'$var = FALSE;';
View
2 tests/Standards/AbstractSniffUnitTest.php
@@ -118,7 +118,7 @@ protected function shouldSkipTest()
// Get them in order.
sort($testFiles);
- self::$phpcs->process(array(), $standardName, array($sniffClass));
+ self::$phpcs->process(array(), $standardName, array(strtolower($sniffClass)));
self::$phpcs->setIgnorePatterns(array());
$failureMessages = array();

0 comments on commit bd7033e

Please sign in to comment.
Something went wrong with that request. Please try again.