3
* PHP_CodeSniffer tokenises PHP code and detects violations of a
4
* defined set of coding standards.
9
* @package PHP_CodeSniffer
10
* @author Greg Sherwood <gsherwood@squiz.net>
11
* @author Marc McIntyre <mmcintyre@squiz.net>
12
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
13
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
14
* @link http://pear.php.net/package/PHP_CodeSniffer
17
spl_autoload_register(array('PHP_CodeSniffer', 'autoload'));
19
if (class_exists('PHP_CodeSniffer_Exception', true) === false) {
20
throw new Exception('Class PHP_CodeSniffer_Exception not found');
23
if (class_exists('PHP_CodeSniffer_File', true) === false) {
24
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found');
27
if (class_exists('PHP_CodeSniffer_Tokens', true) === false) {
28
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found');
31
if (class_exists('PHP_CodeSniffer_CLI', true) === false) {
32
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CLI not found');
35
if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) {
36
throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found');
40
* PHP_CodeSniffer tokenises PHP code and detects violations of a
41
* defined set of coding standards.
43
* Standards are specified by classes that implement the PHP_CodeSniffer_Sniff
44
* interface. A sniff registers what token types it wishes to listen for, then
45
* PHP_CodeSniffer encounters that token, the sniff is invoked and passed
46
* information about where the token was found in the stack, and the token stack
49
* Sniff files and their containing class must be prefixed with Sniff, and
50
* have an extension of .php.
52
* Multiple PHP_CodeSniffer operations can be performed by re-calling the
53
* process function with different parameters.
56
* @package PHP_CodeSniffer
57
* @author Greg Sherwood <gsherwood@squiz.net>
58
* @author Marc McIntyre <mmcintyre@squiz.net>
59
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
60
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
61
* @version Release: @package_version@
62
* @link http://pear.php.net/package/PHP_CodeSniffer
68
* The current version.
72
const VERSION = '1.5.5';
75
* Package stability; either stable or beta.
79
const STABILITY = 'stable';
82
* The file or directory that is currently being processed.
89
* A cache of different token types, resolved into arrays.
92
* @see standardiseToken()
94
private static $_resolveTokenCache = array();
97
* The directories that the processed rulesets are in.
99
* This is declared static because it is also used in the
100
* autoloader to look for sniffs outside the PHPCS install.
101
* This way, standards designed to be installed inside PHPCS can
102
* also be used from outside the PHPCS Standards directory.
106
protected static $rulesetDirs = array();
109
* The CLI object controlling the run.
111
* @var PHP_CodeSniffer_CLI
116
* The Reporting object controlling report generation.
118
* @var PHP_CodeSniffer_Reporting
120
public $reporting = null;
123
* An array of sniff objects that are being used to check files.
125
* @var array(PHP_CodeSniffer_Sniff)
127
protected $listeners = array();
130
* An array of sniffs that are being used to check files.
134
protected $sniffs = array();
137
* The listeners array, indexed by token type.
141
private $_tokenListeners = array();
144
* An array of rules from the ruleset.xml file.
146
* It may be empty, indicating that the ruleset does not override
147
* any of the default sniff settings.
151
protected $ruleset = array();
154
* An array of patterns to use for skipping files.
158
protected $ignorePatterns = array();
161
* An array of extensions for files we will check.
165
public $allowedFileExtensions = array(
173
* An array of variable types for param/var we will check.
177
public static $allowedTypes = array(
191
* Constructs a PHP_CodeSniffer object.
193
* @param int $verbosity The verbosity level.
194
* 1: Print progress information.
195
* 2: Print tokenizer debug information.
196
* 3: Print sniff debug information.
197
* @param int $tabWidth The number of spaces each tab represents.
198
* If greater than zero, tabs will be replaced
199
* by spaces before testing each file.
200
* @param string $encoding The charset of the sniffed files.
201
* This is important for some reports that output
202
* with utf-8 encoding as you don't want it double
204
* @param bool $interactive If TRUE, will stop after each file with errors
205
* and wait for user input.
209
public function __construct(
212
$encoding='iso-8859-1',
215
if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
216
define('PHP_CODESNIFFER_VERBOSITY', $verbosity);
219
if (defined('PHP_CODESNIFFER_TAB_WIDTH') === false) {
220
define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth);
223
if (defined('PHP_CODESNIFFER_ENCODING') === false) {
224
define('PHP_CODESNIFFER_ENCODING', $encoding);
227
if (defined('PHP_CODESNIFFER_INTERACTIVE') === false) {
228
define('PHP_CODESNIFFER_INTERACTIVE', $interactive);
231
if (defined('PHPCS_DEFAULT_ERROR_SEV') === false) {
232
define('PHPCS_DEFAULT_ERROR_SEV', 5);
235
if (defined('PHPCS_DEFAULT_WARN_SEV') === false) {
236
define('PHPCS_DEFAULT_WARN_SEV', 5);
239
// Set default CLI object in case someone is running us
240
// without using the command line script.
241
$this->cli = new PHP_CodeSniffer_CLI();
242
$this->cli->errorSeverity = PHPCS_DEFAULT_ERROR_SEV;
243
$this->cli->warningSeverity = PHPCS_DEFAULT_WARN_SEV;
244
$this->cli->dieOnUnknownArg = false;
246
$this->reporting = new PHP_CodeSniffer_Reporting();
252
* Autoload static method for loading classes and interfaces.
254
* @param string $className The name of the class or interface.
258
public static function autoload($className)
260
if (substr($className, 0, 4) === 'PHP_') {
261
$newClassName = substr($className, 4);
263
$newClassName = $className;
266
$path = str_replace(array('_', '\\'), '/', $newClassName).'.php';
268
if (is_file(dirname(__FILE__).'/'.$path) === true) {
269
// Check standard file locations based on class name.
270
include dirname(__FILE__).'/'.$path;
271
} else if (is_file(dirname(__FILE__).'/CodeSniffer/Standards/'.$path) === true) {
272
// Check for included sniffs.
273
include dirname(__FILE__).'/CodeSniffer/Standards/'.$path;
275
// Check standard file locations based on the loaded rulesets.
276
foreach (self::$rulesetDirs as $rulesetDir) {
277
if (is_file(dirname($rulesetDir).'/'.$path) === true) {
278
include_once dirname($rulesetDir).'/'.$path;
291
* Sets an array of file extensions that we will allow checking of.
293
* If the extension is one of the defaults, a specific tokenizer
294
* will be used. Otherwise, the PHP tokenizer will be used for
295
* all extensions passed.
297
* @param array $extensions An array of file extensions.
301
public function setAllowedFileExtensions(array $extensions)
303
$newExtensions = array();
304
foreach ($extensions as $ext) {
305
if (isset($this->allowedFileExtensions[$ext]) === true) {
306
$newExtensions[$ext] = $this->allowedFileExtensions[$ext];
308
$newExtensions[$ext] = 'PHP';
312
$this->allowedFileExtensions = $newExtensions;
314
}//end setAllowedFileExtensions()
318
* Sets an array of ignore patterns that we use to skip files and folders.
320
* Patterns are not case sensitive.
322
* @param array $patterns An array of ignore patterns. The pattern is the key
323
* and the value is either "absolute" or "relative",
324
* depending on how the pattern should be applied to a
329
public function setIgnorePatterns(array $patterns)
331
$this->ignorePatterns = $patterns;
333
}//end setIgnorePatterns()
337
* Gets the array of ignore patterns.
339
* Optionally takes a listener to get ignore patterns specified
340
* for that sniff only.
342
* @param string $listener The listener to get patterns for. If NULL, all
343
* patterns are returned.
347
public function getIgnorePatterns($listener=null)
349
if ($listener === null) {
350
return $this->ignorePatterns;
353
if (isset($this->ignorePatterns[$listener]) === true) {
354
return $this->ignorePatterns[$listener];
359
}//end getIgnorePatterns()
363
* Sets the internal CLI object.
365
* @param object $cli The CLI object controlling the run.
369
public function setCli($cli)
377
* Processes the files/directories that PHP_CodeSniffer was constructed with.
379
* @param string|array $files The files and directories to process. For
380
* directories, each sub directory will also
381
* be traversed for source files.
382
* @param string|array $standards The set of code sniffs we are testing
384
* @param array $restrictions The sniff codes to restrict the
386
* @param boolean $local If true, don't recurse into directories.
389
* @throws PHP_CodeSniffer_Exception If files or standard are invalid.
391
public function process($files, $standards, array $restrictions=array(), $local=false)
393
if (is_array($files) === false) {
394
$files = array($files);
397
if (is_array($standards) === false) {
398
$standards = array($standards);
401
// Reset the members.
402
$this->listeners = array();
403
$this->sniffs = array();
404
$this->ruleset = array();
405
$this->_tokenListeners = array();
406
self::$rulesetDirs = array();
408
// Ensure this option is enabled or else line endings will not always
409
// be detected properly for files created on a Mac with the /r line ending.
410
ini_set('auto_detect_line_endings', true);
413
foreach ($standards as $standard) {
414
$installed = $this->getInstalledStandardPath($standard);
415
if ($installed !== null) {
416
$standard = $installed;
417
} else if (is_dir($standard) === true
418
&& is_file(realpath($standard.'/ruleset.xml')) === true
420
$standard = realpath($standard.'/ruleset.xml');
423
if (PHP_CODESNIFFER_VERBOSITY === 1) {
424
$ruleset = simplexml_load_file($standard);
425
if ($ruleset !== false) {
426
$standardName = (string) $ruleset['name'];
429
echo "Registering sniffs in the $standardName standard... ";
430
if (count($standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) {
435
$sniffs = array_merge($sniffs, $this->processRuleset($standard));
438
$sniffRestrictions = array();
439
foreach ($restrictions as $sniffCode) {
440
$parts = explode('.', strtolower($sniffCode));
441
$sniffRestrictions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff';
444
$this->registerSniffs($sniffs, $sniffRestrictions);
445
$this->populateTokenListeners();
447
if (PHP_CODESNIFFER_VERBOSITY === 1) {
448
$numSniffs = count($this->sniffs);
449
echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
452
// The SVN pre-commit calls process() to init the sniffs
453
// and ruleset so there may not be any files to process.
454
// But this has to come after that initial setup.
455
if (empty($files) === true) {
459
$cliValues = $this->cli->getCommandLineValues();
460
$showProgress = $cliValues['showProgress'];
462
if (PHP_CODESNIFFER_VERBOSITY > 0) {
463
echo 'Creating file list... ';
466
$todo = $this->getFilesToProcess($files, $local);
467
$numFiles = count($todo);
469
if (PHP_CODESNIFFER_VERBOSITY > 0) {
470
echo "DONE ($numFiles files in queue)".PHP_EOL;
475
$maxLength = strlen($numFiles);
477
foreach ($todo as $file) {
479
$currDir = dirname($file);
480
if ($lastDir !== $currDir) {
481
if (PHP_CODESNIFFER_VERBOSITY > 0) {
482
echo 'Changing into directory '.$currDir.PHP_EOL;
488
$phpcsFile = $this->processFile($file, null, $restrictions);
491
if (PHP_CODESNIFFER_VERBOSITY > 0
492
|| PHP_CODESNIFFER_INTERACTIVE === true
493
|| $showProgress === false
498
// Show progress information.
499
if ($phpcsFile === null) {
502
$errors = $phpcsFile->getErrorCount();
503
$warnings = $phpcsFile->getWarningCount();
506
} else if ($warnings > 0) {
515
$padding = ($maxLength - strlen($numProcessed));
516
echo str_repeat(' ', $padding);
517
$percent = round($numProcessed / $numFiles * 100);
518
echo " $numProcessed / $numFiles ($percent%)".PHP_EOL;
523
if (PHP_CODESNIFFER_VERBOSITY === 0
524
&& PHP_CODESNIFFER_INTERACTIVE === false
525
&& $showProgress === true
527
echo PHP_EOL.PHP_EOL;
534
* Processes a single ruleset and returns a list of the sniffs it represents.
536
* Rules founds within the ruleset are processed immediately, but sniff classes
537
* are not registered by this method.
539
* @param string $rulesetPath The path to a ruleset XML file.
540
* @param int $depth How many nested processing steps we are in. This
541
* is only used for debug output.
544
* @throws PHP_CodeSniffer_Exception If the ruleset path is invalid.
546
public function processRuleset($rulesetPath, $depth=0)
548
$rulesetPath = realpath($rulesetPath);
549
if (PHP_CODESNIFFER_VERBOSITY > 1) {
550
echo str_repeat("\t", $depth);
551
echo "Processing ruleset $rulesetPath".PHP_EOL;
554
$ruleset = simplexml_load_file($rulesetPath);
555
if ($ruleset === false) {
556
throw new PHP_CodeSniffer_Exception("Ruleset $rulesetPath is not valid");
559
$ownSniffs = array();
560
$includedSniffs = array();
561
$excludedSniffs = array();
563
$rulesetDir = dirname($rulesetPath);
564
self::$rulesetDirs[] = $rulesetDir;
566
if (is_dir($rulesetDir.'/Sniffs') === true) {
567
if (PHP_CODESNIFFER_VERBOSITY > 1) {
568
echo str_repeat("\t", $depth);
569
echo "\tAdding sniff files from \"/.../".basename($rulesetDir)."/Sniffs/\" directory".PHP_EOL;
572
$ownSniffs = $this->_expandSniffDirectory($rulesetDir.'/Sniffs', $depth);
575
foreach ($ruleset->rule as $rule) {
576
if (isset($rule['ref']) === false) {
580
if (PHP_CODESNIFFER_VERBOSITY > 1) {
581
echo str_repeat("\t", $depth);
582
echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL;
585
$includedSniffs = array_merge(
587
$this->_expandRulesetReference($rule['ref'], $rulesetDir, $depth)
590
if (isset($rule->exclude) === true) {
591
foreach ($rule->exclude as $exclude) {
592
if (PHP_CODESNIFFER_VERBOSITY > 1) {
593
echo str_repeat("\t", $depth);
594
echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL;
597
// Check if a single code is being excluded, which is a shortcut
598
// for setting the severity of the message to 0.
599
$parts = explode('.', $exclude['name']);
600
if (count($parts) === 4) {
601
$this->ruleset[(string) $exclude['name']]['severity'] = 0;
602
if (PHP_CODESNIFFER_VERBOSITY > 1) {
603
echo str_repeat("\t", $depth);
604
echo "\t\t=> severity set to 0".PHP_EOL;
607
$excludedSniffs = array_merge(
609
$this->_expandRulesetReference($exclude['name'], $rulesetDir, ($depth + 1))
615
$this->_processRule($rule, $depth);
618
// Process custom ignore pattern rules.
619
foreach ($ruleset->{'config'} as $config) {
620
$this->setConfigData((string) $config['name'], (string) $config['value'], true);
621
if (PHP_CODESNIFFER_VERBOSITY > 1) {
622
echo str_repeat("\t", $depth);
623
echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL;
627
// Process custom ignore pattern rules.
628
foreach ($ruleset->{'exclude-pattern'} as $pattern) {
629
if (isset($pattern['type']) === false) {
630
$pattern['type'] = 'absolute';
633
$this->ignorePatterns[(string) $pattern] = (string) $pattern['type'];
634
if (PHP_CODESNIFFER_VERBOSITY > 1) {
635
echo str_repeat("\t", $depth);
636
echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
640
$includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs));
641
$excludedSniffs = array_unique($excludedSniffs);
643
if (PHP_CODESNIFFER_VERBOSITY > 1) {
644
$included = count($includedSniffs);
645
$excluded = count($excludedSniffs);
646
echo str_repeat("\t", $depth);
647
echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL;
650
// Merge our own sniff list with our externally included
651
// sniff list, but filter out any excluded sniffs.
653
foreach ($includedSniffs as $sniff) {
654
if (in_array($sniff, $excludedSniffs) === true) {
657
$files[] = realpath($sniff);
663
}//end processRuleset()
667
* Expands a directory into a list of sniff files within.
669
* @param string $directory The path to a directory.
670
* @param int $depth How many nested processing steps we are in. This
671
* is only used for debug output.
675
private function _expandSniffDirectory($directory, $depth=0)
679
if (defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') === true) {
680
$rdi = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
682
$rdi = new RecursiveDirectoryIterator($directory);
685
$di = new RecursiveIteratorIterator($rdi, 0, RecursiveIteratorIterator::CATCH_GET_CHILD);
687
$dirLen = strlen($directory);
689
foreach ($di as $file) {
690
$filename = $file->getFilename();
692
// Skip hidden files.
693
if (substr($filename, 0, 1) === '.') {
697
// We are only interested in PHP and sniff files.
698
$fileParts = explode('.', $filename);
699
if (array_pop($fileParts) !== 'php') {
703
$basename = basename($filename, '.php');
704
if (substr($basename, -5) !== 'Sniff') {
708
$path = $file->getPathname();
710
// Skip files in hidden directories within the Sniffs directory of this
711
// standard. We use the offset with strpos() to allow hidden directories
712
// before, valid example:
713
// /home/foo/.composer/vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/...
714
if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) {
718
if (PHP_CODESNIFFER_VERBOSITY > 1) {
719
echo str_repeat("\t", $depth);
720
echo "\t\t=> $path".PHP_EOL;
728
}//end _expandSniffDirectory()
732
* Expands a ruleset reference into a list of sniff files.
734
* @param string $ref The reference from the ruleset XML file.
735
* @param string $rulesetDir The directory of the ruleset XML file, used to
736
* evaluate relative paths.
737
* @param int $depth How many nested processing steps we are in. This
738
* is only used for debug output.
741
* @throws PHP_CodeSniffer_Exception If the reference is invalid.
743
private function _expandRulesetReference($ref, $rulesetDir, $depth=0)
745
// Ignore internal sniffs codes as they are used to only
746
// hide and change internal messages.
747
if (substr($ref, 0, 9) === 'Internal.') {
748
if (PHP_CODESNIFFER_VERBOSITY > 1) {
749
echo str_repeat("\t", $depth);
750
echo "\t\t* ignoring internal sniff code *".PHP_EOL;
756
// As sniffs can't begin with a full stop, assume references in
757
// this format are relative paths and attempt to convert them
758
// to absolute paths. If this fails, let the reference run through
759
// the normal checks and have it fail as normal.
760
if (substr($ref, 0, 1) === '.') {
761
$realpath = realpath($rulesetDir.'/'.$ref);
762
if ($realpath !== false) {
764
if (PHP_CODESNIFFER_VERBOSITY > 1) {
765
echo str_repeat("\t", $depth);
766
echo "\t\t=> $ref".PHP_EOL;
771
if (is_file($ref) === false) {
772
// See if this is a whole standard being referenced.
773
$path = $this->getInstalledStandardPath($ref);
774
if ($path !== null) {
776
if (PHP_CODESNIFFER_VERBOSITY > 1) {
777
echo str_repeat("\t", $depth);
778
echo "\t\t=> $ref".PHP_EOL;
780
} else if (is_dir($ref) === false) {
781
// Work out the sniff path.
782
$sepPos = strpos($ref, DIRECTORY_SEPARATOR);
783
if ($sepPos !== false) {
784
$stdName = substr($ref, 0, $sepPos);
785
$path = substr($ref, $sepPos);
787
$parts = explode('.', $ref);
788
$stdName = $parts[0];
789
if (count($parts) === 1) {
792
} else if (count($parts) === 2) {
793
// A directory of sniffs?
794
$path = '/Sniffs/'.$parts[1];
797
$path = '/Sniffs/'.$parts[1].'/'.$parts[2].'Sniff.php';
802
$stdPath = $this->getInstalledStandardPath($stdName);
803
if ($stdPath !== null && $path !== '') {
804
$newRef = realpath(dirname($stdPath).$path);
807
if ($newRef === false) {
808
// The sniff is not locally installed, so check if it is being
809
// referenced as a remote sniff outside the install. We do this
810
// by looking through all directories where we have found ruleset
811
// files before, looking for ones for this particular standard,
812
// and seeing if it is in there.
813
foreach (self::$rulesetDirs as $dir) {
814
if (strtolower(basename($dir)) !== strtolower($stdName)) {
818
$newRef = realpath($dir.$path);
820
if ($newRef !== false) {
828
if (PHP_CODESNIFFER_VERBOSITY > 1) {
829
echo str_repeat("\t", $depth);
830
echo "\t\t=> $ref".PHP_EOL;
835
if (is_dir($ref) === true) {
836
if (is_file($ref.'/ruleset.xml') === true) {
837
// We are referencing an external coding standard.
838
if (PHP_CODESNIFFER_VERBOSITY > 1) {
839
echo str_repeat("\t", $depth);
840
echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL;
843
return $this->processRuleset($ref.'/ruleset.xml', ($depth + 2));
845
// We are referencing a whole directory of sniffs.
846
if (PHP_CODESNIFFER_VERBOSITY > 1) {
847
echo str_repeat("\t", $depth);
848
echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL;
849
echo str_repeat("\t", $depth);
850
echo "\t\tAdding sniff files from directory".PHP_EOL;
853
return $this->_expandSniffDirectory($ref, ($depth + 1));
856
if (is_file($ref) === false) {
857
$error = "Referenced sniff \"$ref\" does not exist";
858
throw new PHP_CodeSniffer_Exception($error);
861
if (substr($ref, -9) === 'Sniff.php') {
865
// Assume an external ruleset.xml file.
866
if (PHP_CODESNIFFER_VERBOSITY > 1) {
867
echo str_repeat("\t", $depth);
868
echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL;
871
return $this->processRuleset($ref, ($depth + 2));
875
}//end _expandRulesetReference()
879
* Processes a rule from a ruleset XML file, overriding built-in defaults.
881
* @param SimpleXMLElement $rule The rule object from a ruleset XML file.
882
* @param int $depth How many nested processing steps we are in.
883
* This is only used for debug output.
887
private function _processRule($rule, $depth=0)
889
$code = (string) $rule['ref'];
892
if (isset($rule->severity) === true) {
893
if (isset($this->ruleset[$code]) === false) {
894
$this->ruleset[$code] = array();
897
$this->ruleset[$code]['severity'] = (int) $rule->severity;
898
if (PHP_CODESNIFFER_VERBOSITY > 1) {
899
echo str_repeat("\t", $depth);
900
echo "\t\t=> severity set to ".(int) $rule->severity.PHP_EOL;
904
// Custom message type.
905
if (isset($rule->type) === true) {
906
if (isset($this->ruleset[$code]) === false) {
907
$this->ruleset[$code] = array();
910
$this->ruleset[$code]['type'] = (string) $rule->type;
911
if (PHP_CODESNIFFER_VERBOSITY > 1) {
912
echo str_repeat("\t", $depth);
913
echo "\t\t=> message type set to ".(string) $rule->type.PHP_EOL;
918
if (isset($rule->message) === true) {
919
if (isset($this->ruleset[$code]) === false) {
920
$this->ruleset[$code] = array();
923
$this->ruleset[$code]['message'] = (string) $rule->message;
924
if (PHP_CODESNIFFER_VERBOSITY > 1) {
925
echo str_repeat("\t", $depth);
926
echo "\t\t=> message set to ".(string) $rule->message.PHP_EOL;
930
// Custom properties.
931
if (isset($rule->properties) === true) {
932
foreach ($rule->properties->property as $prop) {
933
if (isset($this->ruleset[$code]) === false) {
934
$this->ruleset[$code] = array(
935
'properties' => array(),
937
} else if (isset($this->ruleset[$code]['properties']) === false) {
938
$this->ruleset[$code]['properties'] = array();
941
$name = (string) $prop['name'];
942
if (isset($prop['type']) === true
943
&& (string) $prop['type'] === 'array'
945
$value = (string) $prop['value'];
946
$this->ruleset[$code]['properties'][$name] = explode(',', $value);
947
if (PHP_CODESNIFFER_VERBOSITY > 1) {
948
echo str_repeat("\t", $depth);
949
echo "\t\t=> array property \"$name\" set to \"$value\"".PHP_EOL;
952
$this->ruleset[$code]['properties'][$name] = (string) $prop['value'];
953
if (PHP_CODESNIFFER_VERBOSITY > 1) {
954
echo str_repeat("\t", $depth);
955
echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"'.PHP_EOL;
962
foreach ($rule->{'exclude-pattern'} as $pattern) {
963
if (isset($this->ignorePatterns[$code]) === false) {
964
$this->ignorePatterns[$code] = array();
967
if (isset($pattern['type']) === false) {
968
$pattern['type'] = 'absolute';
971
$this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type'];
972
if (PHP_CODESNIFFER_VERBOSITY > 1) {
973
echo str_repeat("\t", $depth);
974
echo "\t\t=> added sniff-specific ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
978
}//end _processRule()
982
* Loads and stores sniffs objects used for sniffing files.
984
* @param array $files Paths to the sniff files to register.
985
* @param array $restrictions The sniff class names to restrict the allowed
990
public function registerSniffs($files, $restrictions)
992
$listeners = array();
994
foreach ($files as $file) {
995
// Work out where the position of /StandardName/Sniffs/... is
996
// so we can determine what the class will be called.
997
$sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
998
if ($sniffPos === false) {
1002
$slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
1003
if ($slashPos === false) {
1007
$className = substr($file, ($slashPos + 1));
1008
$className = substr($className, 0, -4);
1009
$className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
1011
// If they have specified a list of sniffs to restrict to, check
1012
// to see if this sniff is allowed.
1013
if (empty($restrictions) === false
1014
&& in_array(strtolower($className), $restrictions) === false
1021
// Support the use of PHP namespaces. If the class name we included
1022
// contains namespace separators instead of underscores, use this as the
1023
// class name from now on.
1024
$classNameNS = str_replace('_', '\\', $className);
1025
if (class_exists($classNameNS, false) === true) {
1026
$className = $classNameNS;
1029
$listeners[$className] = $className;
1031
if (PHP_CODESNIFFER_VERBOSITY > 2) {
1032
echo "Registered $className".PHP_EOL;
1036
$this->sniffs = $listeners;
1038
}//end registerSniffs()
1042
* Populates the array of PHP_CodeSniffer_Sniff's for this file.
1045
* @throws PHP_CodeSniffer_Exception If sniff registration fails.
1047
public function populateTokenListeners()
1049
// Construct a list of listeners indexed by token being listened for.
1050
$this->_tokenListeners = array();
1052
foreach ($this->sniffs as $listenerClass) {
1053
// Work out the internal code for this sniff. Detect usage of namespace
1054
// separators instead of underscores to support PHP namespaces.
1055
if (strstr($listenerClass, '\\') === false) {
1056
$parts = explode('_', $listenerClass);
1058
$parts = explode('\\', $listenerClass);
1061
$code = $parts[0].'.'.$parts[2].'.'.$parts[3];
1062
$code = substr($code, 0, -5);
1064
$this->listeners[$listenerClass] = new $listenerClass();
1066
// Set custom properties.
1067
if (isset($this->ruleset[$code]['properties']) === true) {
1068
foreach ($this->ruleset[$code]['properties'] as $name => $value) {
1069
$this->setSniffProperty($listenerClass, $name, $value);
1073
$tokenizers = array('PHP');
1074
$vars = get_class_vars($listenerClass);
1075
if (isset($vars['supportedTokenizers']) === true) {
1076
$tokenizers = $vars['supportedTokenizers'];
1079
$tokens = $this->listeners[$listenerClass]->register();
1080
if (is_array($tokens) === false) {
1081
$msg = "Sniff $listenerClass register() method must return an array";
1082
throw new PHP_CodeSniffer_Exception($msg);
1085
foreach ($tokens as $token) {
1086
if (isset($this->_tokenListeners[$token]) === false) {
1087
$this->_tokenListeners[$token] = array();
1090
if (in_array($this->listeners[$listenerClass], $this->_tokenListeners[$token], true) === false) {
1091
$this->_tokenListeners[$token][] = array(
1092
'listener' => $this->listeners[$listenerClass],
1093
'class' => $listenerClass,
1094
'tokenizers' => $tokenizers,
1100
}//end populateTokenListeners()
1104
* Set a single property for a sniff.
1106
* @param string $listenerClass The class name of the sniff.
1107
* @param string $name The name of the property to change.
1108
* @param string $value The new value of the property.
1112
public function setSniffProperty($listenerClass, $name, $value)
1114
// Setting a property for a sniff we are not using.
1115
if (isset($this->listeners[$listenerClass]) === false) {
1119
$name = trim($name);
1120
if (is_string($value) === true) {
1121
$value = trim($value);
1124
// Special case for booleans.
1125
if ($value === 'true') {
1127
} else if ($value === 'false') {
1131
$this->listeners[$listenerClass]->$name = $value;
1133
}//end setSniffProperty()
1137
* Get a list of files that will be processed.
1139
* If passed directories, this method will find all files within them.
1140
* The method will also perform file extension and ignore pattern filtering.
1142
* @param string $paths A list of file or directory paths to process.
1143
* @param boolean $local If true, only process 1 level of files in directories
1146
* @throws Exception If there was an error opening a directory.
1147
* @see shouldProcessFile()
1149
public function getFilesToProcess($paths, $local=false)
1153
foreach ($paths as $path) {
1154
if (is_dir($path) === true) {
1155
if ($local === true) {
1156
$di = new DirectoryIterator($path);
1158
$di = new RecursiveIteratorIterator(
1159
new RecursiveDirectoryIterator($path),
1161
RecursiveIteratorIterator::CATCH_GET_CHILD
1165
foreach ($di as $file) {
1166
// Check if the file exists after all symlinks are resolved.
1167
$filePath = realpath($file->getPathname());
1168
if ($filePath === false) {
1172
if (is_dir($filePath) === true) {
1176
if ($this->shouldProcessFile($file->getPathname(), $path) === false) {
1180
$files[] = $file->getPathname();
1183
if ($this->shouldIgnoreFile($path, dirname($path)) === true) {
1193
}//end getFilesToProcess()
1197
* Checks filtering rules to see if a file should be checked.
1199
* Checks both file extension filters and path ignore filters.
1201
* @param string $path The path to the file being checked.
1202
* @param string $basedir The directory to use for relative path checks.
1206
public function shouldProcessFile($path, $basedir)
1208
// Check that the file's extension is one we are checking.
1209
// We are strict about checking the extension and we don't
1210
// let files through with no extension or that start with a dot.
1211
$fileName = basename($path);
1212
$fileParts = explode('.', $fileName);
1213
if ($fileParts[0] === $fileName || $fileParts[0] === '') {
1217
// Checking multi-part file extensions, so need to create a
1218
// complete extension list and make sure one is allowed.
1219
$extensions = array();
1220
array_shift($fileParts);
1221
foreach ($fileParts as $part) {
1222
$extensions[implode('.', $fileParts)] = 1;
1223
array_shift($fileParts);
1226
$matches = array_intersect_key($extensions, $this->allowedFileExtensions);
1227
if (empty($matches) === true) {
1231
// If the file's path matches one of our ignore patterns, skip it.
1232
if ($this->shouldIgnoreFile($path, $basedir) === true) {
1238
}//end shouldProcessFile()
1242
* Checks filtering rules to see if a file should be ignored.
1244
* @param string $path The path to the file being checked.
1245
* @param string $basedir The directory to use for relative path checks.
1249
public function shouldIgnoreFile($path, $basedir)
1251
$relativePath = $path;
1252
if (strpos($path, $basedir) === 0) {
1253
// The +1 cuts off the directory separator as well.
1254
$relativePath = substr($path, (strlen($basedir) + 1));
1257
foreach ($this->ignorePatterns as $pattern => $type) {
1258
if (is_array($type) === true) {
1259
// A sniff specific ignore pattern.
1263
// Maintains backwards compatibility in case the ignore pattern does
1264
// not have a relative/absolute value.
1265
if (is_int($pattern) === true) {
1270
$replacements = array(
1275
// We assume a / directory separator, as do the exclude rules
1276
// most developers write, so we need a special case for any system
1277
// that is different.
1278
if (DIRECTORY_SEPARATOR === '\\') {
1279
$replacements['/'] = '\\\\';
1282
$pattern = strtr($pattern, $replacements);
1284
if ($type === 'relative') {
1285
$testPath = $relativePath;
1290
if (preg_match("|{$pattern}|i", $testPath) === 1) {
1297
}//end shouldIgnoreFile()
1301
* Run the code sniffs over a single given file.
1303
* Processes the file and runs the PHP_CodeSniffer sniffs to verify that it
1304
* conforms with the standard. Returns the processed file object, or NULL
1305
* if no file was processed due to error.
1307
* @param string $file The file to process.
1308
* @param string $contents The contents to parse. If NULL, the content
1309
* is taken from the file system.
1310
* @param array $restrictions The sniff codes to restrict the
1313
* @return PHP_CodeSniffer_File
1314
* @throws PHP_CodeSniffer_Exception If the file could not be processed.
1315
* @see _processFile()
1317
public function processFile($file, $contents=null, $restrictions=array())
1319
if ($contents === null && file_exists($file) === false) {
1320
throw new PHP_CodeSniffer_Exception("Source file $file does not exist");
1323
$filePath = realpath($file);
1324
if ($filePath === false) {
1328
// Before we go and spend time tokenizing this file, just check
1329
// to see if there is a tag up top to indicate that the whole
1330
// file should be ignored. It must be on one of the first two lines.
1331
$firstContent = $contents;
1332
if ($contents === null && is_readable($filePath) === true) {
1333
$handle = fopen($filePath, 'r');
1334
if ($handle !== false) {
1335
$firstContent = fgets($handle);
1336
$firstContent .= fgets($handle);
1339
if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false) {
1340
// We are ignoring the whole file.
1341
if (PHP_CODESNIFFER_VERBOSITY > 0) {
1342
echo 'Ignoring '.basename($filePath).PHP_EOL;
1351
$phpcsFile = $this->_processFile($file, $contents, $restrictions);
1352
} catch (Exception $e) {
1353
$trace = $e->getTrace();
1355
$filename = $trace[0]['args'][0];
1356
if (is_object($filename) === true
1357
&& get_class($filename) === 'PHP_CodeSniffer_File'
1359
$filename = $filename->getFilename();
1360
} else if (is_numeric($filename) === true) {
1361
// See if we can find the PHP_CodeSniffer_File object.
1362
foreach ($trace as $data) {
1363
if (isset($data['args'][0]) === true
1364
&& ($data['args'][0] instanceof PHP_CodeSniffer_File) === true
1366
$filename = $data['args'][0]->getFilename();
1369
} else if (is_string($filename) === false) {
1370
$filename = (string) $filename;
1373
$error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
1375
$phpcsFile = new PHP_CodeSniffer_File(
1377
$this->_tokenListeners,
1378
$this->allowedFileExtensions,
1384
$phpcsFile->addError($error, null);
1387
$cliValues = $this->cli->getCommandLineValues();
1389
if (PHP_CODESNIFFER_INTERACTIVE === false) {
1390
// Cache the report data for this file so we can unset it to save memory.
1391
$this->reporting->cacheFileReport($phpcsFile, $cliValues);
1396
Running interactively.
1397
Print the error report for the current file and then wait for user input.
1400
// Get current violations and then clear the list to make sure
1401
// we only print violations for a single file each time.
1403
while ($numErrors !== 0) {
1404
$numErrors = ($phpcsFile->getErrorCount() + $phpcsFile->getWarningCount());
1405
if ($numErrors === 0) {
1409
$reportClass = $this->reporting->factory('full');
1410
$reportData = $this->reporting->prepareFileReport($phpcsFile);
1411
$reportClass->generateFileReport($reportData, $cliValues['showSources'], $cliValues['reportWidth']);
1413
echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
1414
$input = fgets(STDIN);
1415
$input = trim($input);
1424
// Repopulate the sniffs because some of them save their state
1425
// and only clear it when the file changes, but we are rechecking
1427
$this->populateTokenListeners();
1428
$phpcsFile = $this->_processFile($file, $contents, $restrictions);
1435
}//end processFile()
1439
* Process the sniffs for a single file.
1441
* Does raw processing only. No interactive support or error checking.
1443
* @param string $file The file to process.
1444
* @param string $contents The contents to parse. If NULL, the content
1445
* is taken from the file system.
1446
* @param array $restrictions The sniff codes to restrict the
1449
* @return PHP_CodeSniffer_File
1450
* @see processFile()
1452
private function _processFile($file, $contents, $restrictions)
1454
if (PHP_CODESNIFFER_VERBOSITY > 0) {
1455
$startTime = time();
1456
echo 'Processing '.basename($file).' ';
1457
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1462
$phpcsFile = new PHP_CodeSniffer_File(
1464
$this->_tokenListeners,
1465
$this->allowedFileExtensions,
1471
$phpcsFile->start($contents);
1472
$phpcsFile->cleanUp();
1474
if (PHP_CODESNIFFER_VERBOSITY > 0) {
1475
$timeTaken = (time() - $startTime);
1476
if ($timeTaken === 0) {
1477
echo 'DONE in < 1 second';
1478
} else if ($timeTaken === 1) {
1479
echo 'DONE in 1 second';
1481
echo "DONE in $timeTaken seconds";
1484
$errors = $phpcsFile->getErrorCount();
1485
$warnings = $phpcsFile->getWarningCount();
1486
echo " ($errors errors, $warnings warnings)".PHP_EOL;
1491
}//end _processFile()
1495
* Generates documentation for a coding standard.
1497
* @param string $standard The standard to generate docs for
1498
* @param array $sniffs A list of sniffs to limit the docs to.
1499
* @param string $generator The name of the generator class to use.
1503
public function generateDocs($standard, array $sniffs=array(), $generator='Text')
1505
if (class_exists('PHP_CodeSniffer_DocGenerators_'.$generator, true) === false) {
1506
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_DocGenerators_'.$generator.' not found');
1509
$class = "PHP_CodeSniffer_DocGenerators_$generator";
1510
$generator = new $class($standard, $sniffs);
1512
$generator->generate();
1514
}//end generateDocs()
1518
* Gets the array of PHP_CodeSniffer_Sniff's.
1520
* @return PHP_CodeSniffer_Sniff[]
1522
public function getSniffs()
1524
return $this->listeners;
1530
* Gets the array of PHP_CodeSniffer_Sniff's indexed by token type.
1534
public function getTokenSniffs()
1536
return $this->_tokenListeners;
1538
}//end getTokenSniffs()
1542
* Takes a token produced from <code>token_get_all()</code> and produces a
1543
* more uniform token.
1545
* Note that this method also resolves T_STRING tokens into more discrete
1546
* types, therefore there is no need to call resolveTstringToken()
1548
* @param string|array $token The token to convert.
1550
* @return array The new token.
1552
public static function standardiseToken($token)
1554
if (is_array($token) === false) {
1555
if (isset(self::$_resolveTokenCache[$token]) === true) {
1556
$newToken = self::$_resolveTokenCache[$token];
1558
$newToken = self::resolveSimpleToken($token);
1561
switch ($token[0]) {
1563
// Some T_STRING tokens can be more specific.
1564
$tokenType = strtolower($token[1]);
1565
if (isset(self::$_resolveTokenCache[$tokenType]) === true) {
1566
$newToken = self::$_resolveTokenCache[$tokenType];
1568
$newToken = self::resolveTstringToken($tokenType);
1574
'code' => T_OPEN_CURLY_BRACKET,
1575
'type' => 'T_OPEN_CURLY_BRACKET',
1580
'code' => $token[0],
1581
'type' => token_name($token[0]),
1586
$newToken['content'] = $token[1];
1591
}//end standardiseToken()
1595
* Converts T_STRING tokens into more usable token names.
1597
* The token should be produced using the token_get_all() function.
1598
* Currently, not all T_STRING tokens are converted.
1600
* @param string $token The T_STRING token to convert as constructed
1601
* by token_get_all().
1603
* @return array The new token.
1605
public static function resolveTstringToken($token)
1607
$newToken = array();
1610
$newToken['type'] = 'T_FALSE';
1613
$newToken['type'] = 'T_TRUE';
1616
$newToken['type'] = 'T_NULL';
1619
$newToken['type'] = 'T_SELF';
1622
$newToken['type'] = 'T_PARENT';
1625
$newToken['type'] = 'T_STRING';
1629
$newToken['code'] = constant($newToken['type']);
1631
self::$_resolveTokenCache[$token] = $newToken;
1634
}//end resolveTstringToken()
1638
* Converts simple tokens into a format that conforms to complex tokens
1639
* produced by token_get_all().
1641
* Simple tokens are tokens that are not in array form when produced from
1644
* @param string $token The simple token to convert.
1646
* @return array The new token in array format.
1648
public static function resolveSimpleToken($token)
1650
$newToken = array();
1654
$newToken['type'] = 'T_OPEN_CURLY_BRACKET';
1657
$newToken['type'] = 'T_CLOSE_CURLY_BRACKET';
1660
$newToken['type'] = 'T_OPEN_SQUARE_BRACKET';
1663
$newToken['type'] = 'T_CLOSE_SQUARE_BRACKET';
1666
$newToken['type'] = 'T_OPEN_PARENTHESIS';
1669
$newToken['type'] = 'T_CLOSE_PARENTHESIS';
1672
$newToken['type'] = 'T_COLON';
1675
$newToken['type'] = 'T_STRING_CONCAT';
1678
$newToken['type'] = 'T_INLINE_THEN';
1681
$newToken['type'] = 'T_SEMICOLON';
1684
$newToken['type'] = 'T_EQUAL';
1687
$newToken['type'] = 'T_MULTIPLY';
1690
$newToken['type'] = 'T_DIVIDE';
1693
$newToken['type'] = 'T_PLUS';
1696
$newToken['type'] = 'T_MINUS';
1699
$newToken['type'] = 'T_MODULUS';
1702
$newToken['type'] = 'T_POWER';
1705
$newToken['type'] = 'T_BITWISE_AND';
1708
$newToken['type'] = 'T_BITWISE_OR';
1711
$newToken['type'] = 'T_LESS_THAN';
1714
$newToken['type'] = 'T_GREATER_THAN';
1717
$newToken['type'] = 'T_BOOLEAN_NOT';
1720
$newToken['type'] = 'T_COMMA';
1723
$newToken['type'] = 'T_ASPERAND';
1726
$newToken['type'] = 'T_DOLLAR';
1729
$newToken['type'] = 'T_BACKTICK';
1732
$newToken['type'] = 'T_NONE';
1736
$newToken['code'] = constant($newToken['type']);
1737
$newToken['content'] = $token;
1739
self::$_resolveTokenCache[$token] = $newToken;
1742
}//end resolveSimpleToken()
1746
* Returns true if the specified string is in the camel caps format.
1748
* @param string $string The string the verify.
1749
* @param boolean $classFormat If true, check to see if the string is in the
1750
* class format. Class format strings must start
1751
* with a capital letter and contain no
1753
* @param boolean $public If true, the first character in the string
1754
* must be an a-z character. If false, the
1755
* character must be an underscore. This
1756
* argument is only applicable if $classFormat
1758
* @param boolean $strict If true, the string must not have two capital
1759
* letters next to each other. If false, a
1760
* relaxed camel caps policy is used to allow
1765
public static function isCamelCaps(
1771
// Check the first character first.
1772
if ($classFormat === false) {
1773
$legalFirstChar = '';
1774
if ($public === false) {
1775
$legalFirstChar = '[_]';
1778
if ($strict === false) {
1779
// Can either start with a lowercase letter, or multiple uppercase
1780
// in a row, representing an acronym.
1781
$legalFirstChar .= '([A-Z]{2,}|[a-z])';
1783
$legalFirstChar .= '[a-z]';
1786
$legalFirstChar = '[A-Z]';
1789
if (preg_match("/^$legalFirstChar/", $string) === 0) {
1793
// Check that the name only contains legal characters.
1794
$legalChars = 'a-zA-Z0-9';
1795
if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
1799
if ($strict === true) {
1800
// Check that there are not two capital letters next to each other.
1801
$length = strlen($string);
1802
$lastCharWasCaps = $classFormat;
1804
for ($i = 1; $i < $length; $i++) {
1805
$ascii = ord($string{$i});
1806
if ($ascii >= 48 && $ascii <= 57) {
1807
// The character is a number, so it cant be a capital.
1810
if (strtoupper($string{$i}) === $string{$i}) {
1817
if ($isCaps === true && $lastCharWasCaps === true) {
1821
$lastCharWasCaps = $isCaps;
1827
}//end isCamelCaps()
1831
* Returns true if the specified string is in the underscore caps format.
1833
* @param string $string The string to verify.
1837
public static function isUnderscoreName($string)
1839
// If there are space in the name, it can't be valid.
1840
if (strpos($string, ' ') !== false) {
1845
$nameBits = explode('_', $string);
1847
if (preg_match('|^[A-Z]|', $string) === 0) {
1848
// Name does not begin with a capital letter.
1851
foreach ($nameBits as $bit) {
1856
if ($bit{0} !== strtoupper($bit{0})) {
1865
}//end isUnderscoreName()
1869
* Returns a valid variable type for param/var tag.
1871
* If type is not one of the standard type, it must be a custom type.
1872
* Returns the correct type name suggestion if type name is invalid.
1874
* @param string $varType The variable type to process.
1878
public static function suggestType($varType)
1880
if ($varType === '') {
1884
if (in_array($varType, self::$allowedTypes) === true) {
1887
$lowerVarType = strtolower($varType);
1888
switch ($lowerVarType) {
1900
if (strpos($lowerVarType, 'array(') !== false) {
1901
// Valid array declaration:
1902
// array, array(type), array(type1 => type2).
1904
$pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i';
1905
if (preg_match($pattern, $varType, $matches) !== 0) {
1907
if (isset($matches[1]) === true) {
1908
$type1 = $matches[1];
1912
if (isset($matches[3]) === true) {
1913
$type2 = $matches[3];
1916
$type1 = self::suggestType($type1);
1917
$type2 = self::suggestType($type2);
1918
if ($type2 !== '') {
1919
$type2 = ' => '.$type2;
1922
return "array($type1$type2)";
1926
} else if (in_array($lowerVarType, self::$allowedTypes) === true) {
1927
// A valid type, but not lower cased.
1928
return $lowerVarType;
1930
// Must be a custom type name.
1935
}//end suggestType()
1939
* Get a list paths where standards are installed.
1943
public static function getInstalledStandardPaths()
1945
$installedPaths = array(dirname(__FILE__).'/CodeSniffer/Standards');
1946
$configPaths = PHP_CodeSniffer::getConfigData('installed_paths');
1947
if ($configPaths !== null) {
1948
$installedPaths = array_merge($installedPaths, explode(',', $configPaths));
1951
$resolvedInstalledPaths = array();
1952
foreach ($installedPaths as $installedPath) {
1953
if (substr($installedPath, 0, 1) === '.') {
1954
$installedPath = dirname(__FILE__).'/'.$installedPath;
1957
$resolvedInstalledPaths[] = $installedPath;
1960
return $resolvedInstalledPaths;
1962
}//end getInstalledStandardPaths()
1966
* Get a list of all coding standards installed.
1968
* Coding standards are directories located in the
1969
* CodeSniffer/Standards directory. Valid coding standards
1970
* include a Sniffs subdirectory.
1972
* @param boolean $includeGeneric If true, the special "Generic"
1973
* coding standard will be included
1975
* @param string $standardsDir A specific directory to look for standards
1976
* in. If not specified, PHP_CodeSniffer will
1977
* look in its default locations.
1980
* @see isInstalledStandard()
1982
public static function getInstalledStandards(
1983
$includeGeneric=false,
1986
$installedStandards = array();
1988
if ($standardsDir === '') {
1989
$installedPaths = self::getInstalledStandardPaths();
1991
$installedPaths = array($standardsDir);
1994
foreach ($installedPaths as $standardsDir) {
1995
$di = new DirectoryIterator($standardsDir);
1996
foreach ($di as $file) {
1997
if ($file->isDir() === true && $file->isDot() === false) {
1998
$filename = $file->getFilename();
2000
// Ignore the special "Generic" standard.
2001
if ($includeGeneric === false && $filename === 'Generic') {
2005
// Valid coding standard dirs include a ruleset.
2006
$csFile = $file->getPathname().'/ruleset.xml';
2007
if (is_file($csFile) === true) {
2008
$installedStandards[] = $filename;
2014
return $installedStandards;
2016
}//end getInstalledStandards()
2020
* Determine if a standard is installed.
2022
* Coding standards are directories located in the
2023
* CodeSniffer/Standards directory. Valid coding standards
2024
* include a ruleset.xml file.
2026
* @param string $standard The name of the coding standard.
2029
* @see getInstalledStandards()
2031
public static function isInstalledStandard($standard)
2033
$path = self::getInstalledStandardPath($standard);
2034
if ($path !== null) {
2037
// This could be a custom standard, installed outside our
2038
// standards directory.
2039
$ruleset = rtrim($standard, ' /\\').DIRECTORY_SEPARATOR.'ruleset.xml';
2040
if (is_file($ruleset) === true) {
2044
// Might also be an actual ruleset file itself.
2045
// If it has an XML extension, let's at least try it.
2046
if (is_file($standard) === true
2047
&& substr(strtolower($standard), -4) === '.xml'
2055
}//end isInstalledStandard()
2059
* Return the path of an installed coding standard.
2061
* Coding standards are directories located in the
2062
* CodeSniffer/Standards directory. Valid coding standards
2063
* include a ruleset.xml file.
2065
* @param string $standard The name of the coding standard.
2067
* @return string|null
2069
public static function getInstalledStandardPath($standard)
2071
$installedPaths = self::getInstalledStandardPaths();
2072
foreach ($installedPaths as $installedPath) {
2073
$path = realpath($installedPath.'/'.$standard.'/ruleset.xml');
2074
if (is_file($path) === true) {
2081
}//end getInstalledStandardPath()
2085
* Get a single config value.
2087
* Config data is stored in the data dir, in a file called
2088
* CodeSniffer.conf. It is a simple PHP array.
2090
* @param string $key The name of the config value.
2092
* @return string|null
2093
* @see setConfigData()
2094
* @see getAllConfigData()
2096
public static function getConfigData($key)
2098
$phpCodeSnifferConfig = self::getAllConfigData();
2100
if ($phpCodeSnifferConfig === null) {
2104
if (isset($phpCodeSnifferConfig[$key]) === false) {
2108
return $phpCodeSnifferConfig[$key];
2110
}//end getConfigData()
2114
* Set a single config value.
2116
* Config data is stored in the data dir, in a file called
2117
* CodeSniffer.conf. It is a simple PHP array.
2119
* @param string $key The name of the config value.
2120
* @param string|null $value The value to set. If null, the config
2121
* entry is deleted, reverting it to the
2123
* @param boolean $temp Set this config data temporarily for this
2124
* script run. This will not write the config
2125
* data to the config file.
2128
* @see getConfigData()
2129
* @throws PHP_CodeSniffer_Exception If the config file can not be written.
2131
public static function setConfigData($key, $value, $temp=false)
2133
if ($temp === false) {
2134
$configFile = dirname(__FILE__).'/CodeSniffer.conf';
2135
if (is_file($configFile) === false
2136
&& strpos('@data_dir@', '@data_dir') === false
2138
// If data_dir was replaced, this is a PEAR install and we can
2139
// use the PEAR data dir to store the conf file.
2140
$configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
2143
if (is_file($configFile) === true
2144
&& is_writable($configFile) === false
2146
$error = "Config file $configFile is not writable";
2147
throw new PHP_CodeSniffer_Exception($error);
2151
$phpCodeSnifferConfig = self::getAllConfigData();
2153
if ($value === null) {
2154
if (isset($phpCodeSnifferConfig[$key]) === true) {
2155
unset($phpCodeSnifferConfig[$key]);
2158
$phpCodeSnifferConfig[$key] = $value;
2161
if ($temp === false) {
2162
$output = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
2163
$output .= var_export($phpCodeSnifferConfig, true);
2164
$output .= "\n?".'>';
2166
if (file_put_contents($configFile, $output) === false) {
2171
$GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
2175
}//end setConfigData()
2179
* Get all config data in an array.
2182
* @see getConfigData()
2184
public static function getAllConfigData()
2186
if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']) === true) {
2187
return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
2190
$configFile = dirname(__FILE__).'/CodeSniffer.conf';
2191
if (is_file($configFile) === false) {
2192
$configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
2195
if (is_file($configFile) === false) {
2199
include $configFile;
2200
$GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig;
2201
return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'];
2203
}//end getAllConfigData()