Mercurial > hg > cmmr2012-drupal-site
comparison vendor/squizlabs/php_codesniffer/CodeSniffer.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | 5311817fb629 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 /** | |
3 * PHP_CodeSniffer tokenizes PHP code and detects violations of a | |
4 * defined set of coding standards. | |
5 * | |
6 * PHP version 5 | |
7 * | |
8 * @category PHP | |
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 | |
15 */ | |
16 | |
17 spl_autoload_register(array('PHP_CodeSniffer', 'autoload')); | |
18 | |
19 if (class_exists('PHP_CodeSniffer_Exception', true) === false) { | |
20 throw new Exception('Class PHP_CodeSniffer_Exception not found'); | |
21 } | |
22 | |
23 if (class_exists('PHP_CodeSniffer_File', true) === false) { | |
24 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found'); | |
25 } | |
26 | |
27 if (class_exists('PHP_CodeSniffer_Fixer', true) === false) { | |
28 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Fixer not found'); | |
29 } | |
30 | |
31 if (class_exists('PHP_CodeSniffer_Tokens', true) === false) { | |
32 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found'); | |
33 } | |
34 | |
35 if (class_exists('PHP_CodeSniffer_CLI', true) === false) { | |
36 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CLI not found'); | |
37 } | |
38 | |
39 if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) { | |
40 throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found'); | |
41 } | |
42 | |
43 /** | |
44 * PHP_CodeSniffer tokenizes PHP code and detects violations of a | |
45 * defined set of coding standards. | |
46 * | |
47 * Standards are specified by classes that implement the PHP_CodeSniffer_Sniff | |
48 * interface. A sniff registers what token types it wishes to listen for, then | |
49 * PHP_CodeSniffer encounters that token, the sniff is invoked and passed | |
50 * information about where the token was found in the stack, and the token stack | |
51 * itself. | |
52 * | |
53 * Sniff files and their containing class must be prefixed with Sniff, and | |
54 * have an extension of .php. | |
55 * | |
56 * Multiple PHP_CodeSniffer operations can be performed by re-calling the | |
57 * process function with different parameters. | |
58 * | |
59 * @category PHP | |
60 * @package PHP_CodeSniffer | |
61 * @author Greg Sherwood <gsherwood@squiz.net> | |
62 * @author Marc McIntyre <mmcintyre@squiz.net> | |
63 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) | |
64 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence | |
65 * @version Release: @package_version@ | |
66 * @link http://pear.php.net/package/PHP_CodeSniffer | |
67 */ | |
68 class PHP_CodeSniffer | |
69 { | |
70 | |
71 /** | |
72 * The current version. | |
73 * | |
74 * @var string | |
75 */ | |
76 const VERSION = '2.8.1'; | |
77 | |
78 /** | |
79 * Package stability; either stable, beta or alpha. | |
80 * | |
81 * @var string | |
82 */ | |
83 const STABILITY = 'stable'; | |
84 | |
85 /** | |
86 * The file or directory that is currently being processed. | |
87 * | |
88 * @var string | |
89 */ | |
90 protected $file = ''; | |
91 | |
92 /** | |
93 * The directories that the processed rulesets are in. | |
94 * | |
95 * This is declared static because it is also used in the | |
96 * autoloader to look for sniffs outside the PHPCS install. | |
97 * This way, standards designed to be installed inside PHPCS can | |
98 * also be used from outside the PHPCS Standards directory. | |
99 * | |
100 * @var string | |
101 */ | |
102 protected static $rulesetDirs = array(); | |
103 | |
104 /** | |
105 * The CLI object controlling the run. | |
106 * | |
107 * @var PHP_CodeSniffer_CLI | |
108 */ | |
109 public $cli = null; | |
110 | |
111 /** | |
112 * The Reporting object controlling report generation. | |
113 * | |
114 * @var PHP_CodeSniffer_Reporting | |
115 */ | |
116 public $reporting = null; | |
117 | |
118 /** | |
119 * An array of sniff objects that are being used to check files. | |
120 * | |
121 * @var array(PHP_CodeSniffer_Sniff) | |
122 */ | |
123 protected $listeners = array(); | |
124 | |
125 /** | |
126 * An array of sniffs that are being used to check files. | |
127 * | |
128 * @var array(string) | |
129 */ | |
130 protected $sniffs = array(); | |
131 | |
132 /** | |
133 * A mapping of sniff codes to fully qualified class names. | |
134 * | |
135 * The key is the sniff code and the value | |
136 * is the fully qualified name of the sniff class. | |
137 * | |
138 * @var array<string, string> | |
139 */ | |
140 public $sniffCodes = array(); | |
141 | |
142 /** | |
143 * The listeners array, indexed by token type. | |
144 * | |
145 * @var array | |
146 */ | |
147 private $_tokenListeners = array(); | |
148 | |
149 /** | |
150 * An array of rules from the ruleset.xml file. | |
151 * | |
152 * It may be empty, indicating that the ruleset does not override | |
153 * any of the default sniff settings. | |
154 * | |
155 * @var array | |
156 */ | |
157 protected $ruleset = array(); | |
158 | |
159 /** | |
160 * An array of patterns to use for skipping files. | |
161 * | |
162 * @var array | |
163 */ | |
164 protected $ignorePatterns = array(); | |
165 | |
166 /** | |
167 * An array of extensions for files we will check. | |
168 * | |
169 * @var array | |
170 */ | |
171 public $allowedFileExtensions = array(); | |
172 | |
173 /** | |
174 * An array of default extensions and associated tokenizers. | |
175 * | |
176 * If no extensions are set, these will be used as the defaults. | |
177 * If extensions are set, these will be used when the correct tokenizer | |
178 * can not be determined, such as when checking a passed filename instead | |
179 * of files in a directory. | |
180 * | |
181 * @var array | |
182 */ | |
183 public $defaultFileExtensions = array( | |
184 'php' => 'PHP', | |
185 'inc' => 'PHP', | |
186 'js' => 'JS', | |
187 'css' => 'CSS', | |
188 ); | |
189 | |
190 /** | |
191 * An array of variable types for param/var we will check. | |
192 * | |
193 * @var array(string) | |
194 */ | |
195 public static $allowedTypes = array( | |
196 'array', | |
197 'boolean', | |
198 'float', | |
199 'integer', | |
200 'mixed', | |
201 'object', | |
202 'string', | |
203 'resource', | |
204 'callable', | |
205 ); | |
206 | |
207 | |
208 /** | |
209 * Constructs a PHP_CodeSniffer object. | |
210 * | |
211 * @param int $verbosity The verbosity level. | |
212 * 1: Print progress information. | |
213 * 2: Print tokenizer debug information. | |
214 * 3: Print sniff debug information. | |
215 * @param int $tabWidth The number of spaces each tab represents. | |
216 * If greater than zero, tabs will be replaced | |
217 * by spaces before testing each file. | |
218 * @param string $encoding The charset of the sniffed files. | |
219 * This is important for some reports that output | |
220 * with utf-8 encoding as you don't want it double | |
221 * encoding messages. | |
222 * @param bool $interactive If TRUE, will stop after each file with errors | |
223 * and wait for user input. | |
224 * | |
225 * @see process() | |
226 */ | |
227 public function __construct( | |
228 $verbosity=0, | |
229 $tabWidth=0, | |
230 $encoding='iso-8859-1', | |
231 $interactive=false | |
232 ) { | |
233 if ($verbosity !== null) { | |
234 $this->setVerbosity($verbosity); | |
235 } | |
236 | |
237 if ($tabWidth !== null) { | |
238 $this->setTabWidth($tabWidth); | |
239 } | |
240 | |
241 if ($encoding !== null) { | |
242 $this->setEncoding($encoding); | |
243 } | |
244 | |
245 if ($interactive !== null) { | |
246 $this->setInteractive($interactive); | |
247 } | |
248 | |
249 if (defined('PHPCS_DEFAULT_ERROR_SEV') === false) { | |
250 define('PHPCS_DEFAULT_ERROR_SEV', 5); | |
251 } | |
252 | |
253 if (defined('PHPCS_DEFAULT_WARN_SEV') === false) { | |
254 define('PHPCS_DEFAULT_WARN_SEV', 5); | |
255 } | |
256 | |
257 if (defined('PHP_CODESNIFFER_CBF') === false) { | |
258 define('PHP_CODESNIFFER_CBF', false); | |
259 } | |
260 | |
261 // Set default CLI object in case someone is running us | |
262 // without using the command line script. | |
263 $this->cli = new PHP_CodeSniffer_CLI(); | |
264 $this->cli->errorSeverity = PHPCS_DEFAULT_ERROR_SEV; | |
265 $this->cli->warningSeverity = PHPCS_DEFAULT_WARN_SEV; | |
266 $this->cli->dieOnUnknownArg = false; | |
267 | |
268 $this->reporting = new PHP_CodeSniffer_Reporting(); | |
269 | |
270 }//end __construct() | |
271 | |
272 | |
273 /** | |
274 * Autoload static method for loading classes and interfaces. | |
275 * | |
276 * @param string $className The name of the class or interface. | |
277 * | |
278 * @return void | |
279 */ | |
280 public static function autoload($className) | |
281 { | |
282 if (substr($className, 0, 4) === 'PHP_') { | |
283 $newClassName = substr($className, 4); | |
284 } else { | |
285 $newClassName = $className; | |
286 } | |
287 | |
288 $path = str_replace(array('_', '\\'), DIRECTORY_SEPARATOR, $newClassName).'.php'; | |
289 | |
290 if (is_file(dirname(__FILE__).DIRECTORY_SEPARATOR.$path) === true) { | |
291 // Check standard file locations based on class name. | |
292 include dirname(__FILE__).DIRECTORY_SEPARATOR.$path; | |
293 return; | |
294 } else { | |
295 // Check for included sniffs. | |
296 $installedPaths = PHP_CodeSniffer::getInstalledStandardPaths(); | |
297 foreach ($installedPaths as $installedPath) { | |
298 if (is_file($installedPath.DIRECTORY_SEPARATOR.$path) === true) { | |
299 include $installedPath.DIRECTORY_SEPARATOR.$path; | |
300 return; | |
301 } | |
302 } | |
303 | |
304 // Check standard file locations based on the loaded rulesets. | |
305 foreach (self::$rulesetDirs as $rulesetDir) { | |
306 if (is_file(dirname($rulesetDir).DIRECTORY_SEPARATOR.$path) === true) { | |
307 include_once dirname($rulesetDir).DIRECTORY_SEPARATOR.$path; | |
308 return; | |
309 } | |
310 } | |
311 }//end if | |
312 | |
313 // Everything else. | |
314 @include $path; | |
315 | |
316 }//end autoload() | |
317 | |
318 | |
319 /** | |
320 * Sets the verbosity level. | |
321 * | |
322 * @param int $verbosity The verbosity level. | |
323 * 1: Print progress information. | |
324 * 2: Print tokenizer debug information. | |
325 * 3: Print sniff debug information. | |
326 * | |
327 * @return void | |
328 */ | |
329 public function setVerbosity($verbosity) | |
330 { | |
331 if (defined('PHP_CODESNIFFER_VERBOSITY') === false) { | |
332 define('PHP_CODESNIFFER_VERBOSITY', $verbosity); | |
333 } | |
334 | |
335 }//end setVerbosity() | |
336 | |
337 | |
338 /** | |
339 * Sets the tab width. | |
340 * | |
341 * @param int $tabWidth The number of spaces each tab represents. | |
342 * If greater than zero, tabs will be replaced | |
343 * by spaces before testing each file. | |
344 * | |
345 * @return void | |
346 */ | |
347 public function setTabWidth($tabWidth) | |
348 { | |
349 if (defined('PHP_CODESNIFFER_TAB_WIDTH') === false) { | |
350 define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth); | |
351 } | |
352 | |
353 }//end setTabWidth() | |
354 | |
355 | |
356 /** | |
357 * Sets the encoding. | |
358 * | |
359 * @param string $encoding The charset of the sniffed files. | |
360 * This is important for some reports that output | |
361 * with utf-8 encoding as you don't want it double | |
362 * encoding messages. | |
363 * | |
364 * @return void | |
365 */ | |
366 public function setEncoding($encoding) | |
367 { | |
368 if (defined('PHP_CODESNIFFER_ENCODING') === false) { | |
369 define('PHP_CODESNIFFER_ENCODING', $encoding); | |
370 } | |
371 | |
372 }//end setEncoding() | |
373 | |
374 | |
375 /** | |
376 * Sets the interactive flag. | |
377 * | |
378 * @param bool $interactive If TRUE, will stop after each file with errors | |
379 * and wait for user input. | |
380 * | |
381 * @return void | |
382 */ | |
383 public function setInteractive($interactive) | |
384 { | |
385 if (defined('PHP_CODESNIFFER_INTERACTIVE') === false) { | |
386 define('PHP_CODESNIFFER_INTERACTIVE', $interactive); | |
387 } | |
388 | |
389 }//end setInteractive() | |
390 | |
391 | |
392 /** | |
393 * Sets an array of file extensions that we will allow checking of. | |
394 * | |
395 * If the extension is one of the defaults, a specific tokenizer | |
396 * will be used. Otherwise, the PHP tokenizer will be used for | |
397 * all extensions passed. | |
398 * | |
399 * @param array $extensions An array of file extensions. | |
400 * | |
401 * @return void | |
402 */ | |
403 public function setAllowedFileExtensions(array $extensions) | |
404 { | |
405 $newExtensions = array(); | |
406 foreach ($extensions as $ext) { | |
407 $slash = strpos($ext, '/'); | |
408 if ($slash !== false) { | |
409 // They specified the tokenizer too. | |
410 list($ext, $tokenizer) = explode('/', $ext); | |
411 $newExtensions[$ext] = strtoupper($tokenizer); | |
412 continue; | |
413 } | |
414 | |
415 if (isset($this->allowedFileExtensions[$ext]) === true) { | |
416 $newExtensions[$ext] = $this->allowedFileExtensions[$ext]; | |
417 } else if (isset($this->defaultFileExtensions[$ext]) === true) { | |
418 $newExtensions[$ext] = $this->defaultFileExtensions[$ext]; | |
419 } else { | |
420 $newExtensions[$ext] = 'PHP'; | |
421 } | |
422 } | |
423 | |
424 $this->allowedFileExtensions = $newExtensions; | |
425 | |
426 }//end setAllowedFileExtensions() | |
427 | |
428 | |
429 /** | |
430 * Sets an array of ignore patterns that we use to skip files and folders. | |
431 * | |
432 * Patterns are not case sensitive. | |
433 * | |
434 * @param array $patterns An array of ignore patterns. The pattern is the key | |
435 * and the value is either "absolute" or "relative", | |
436 * depending on how the pattern should be applied to a | |
437 * file path. | |
438 * | |
439 * @return void | |
440 */ | |
441 public function setIgnorePatterns(array $patterns) | |
442 { | |
443 $this->ignorePatterns = $patterns; | |
444 | |
445 }//end setIgnorePatterns() | |
446 | |
447 | |
448 /** | |
449 * Gets the array of ignore patterns. | |
450 * | |
451 * Optionally takes a listener to get ignore patterns specified | |
452 * for that sniff only. | |
453 * | |
454 * @param string $listener The listener to get patterns for. If NULL, all | |
455 * patterns are returned. | |
456 * | |
457 * @return array | |
458 */ | |
459 public function getIgnorePatterns($listener=null) | |
460 { | |
461 if ($listener === null) { | |
462 return $this->ignorePatterns; | |
463 } | |
464 | |
465 if (isset($this->ignorePatterns[$listener]) === true) { | |
466 return $this->ignorePatterns[$listener]; | |
467 } | |
468 | |
469 return array(); | |
470 | |
471 }//end getIgnorePatterns() | |
472 | |
473 | |
474 /** | |
475 * Sets the internal CLI object. | |
476 * | |
477 * @param object $cli The CLI object controlling the run. | |
478 * | |
479 * @return void | |
480 */ | |
481 public function setCli($cli) | |
482 { | |
483 $this->cli = $cli; | |
484 | |
485 }//end setCli() | |
486 | |
487 | |
488 /** | |
489 * Start a PHP_CodeSniffer run. | |
490 * | |
491 * @param string|array $files The files and directories to process. For | |
492 * directories, each sub directory will also | |
493 * be traversed for source files. | |
494 * @param string|array $standards The set of code sniffs we are testing | |
495 * against. | |
496 * @param array $restrictions The sniff codes to restrict the | |
497 * violations to. | |
498 * @param boolean $local If true, don't recurse into directories. | |
499 * | |
500 * @return void | |
501 */ | |
502 public function process($files, $standards, array $restrictions=array(), $local=false) | |
503 { | |
504 $files = (array) $files; | |
505 $this->initStandard($standards, $restrictions); | |
506 $this->processFiles($files, $local); | |
507 | |
508 }//end process() | |
509 | |
510 | |
511 /** | |
512 * Initialise the standard that the run will use. | |
513 * | |
514 * @param string|array $standards The set of code sniffs we are testing | |
515 * against. | |
516 * @param array $restrictions The sniff codes to restrict the testing to. | |
517 * @param array $exclusions The sniff codes to exclude from testing. | |
518 * | |
519 * @return void | |
520 */ | |
521 public function initStandard($standards, array $restrictions=array(), array $exclusions=array()) | |
522 { | |
523 $standards = (array) $standards; | |
524 | |
525 // Reset the members. | |
526 $this->listeners = array(); | |
527 $this->sniffs = array(); | |
528 $this->ruleset = array(); | |
529 $this->_tokenListeners = array(); | |
530 self::$rulesetDirs = array(); | |
531 | |
532 // Ensure this option is enabled or else line endings will not always | |
533 // be detected properly for files created on a Mac with the /r line ending. | |
534 ini_set('auto_detect_line_endings', true); | |
535 | |
536 if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) { | |
537 // Should be one standard and one sniff being tested at a time. | |
538 $installed = $this->getInstalledStandardPath($standards[0]); | |
539 if ($installed !== null) { | |
540 $standard = $installed; | |
541 } else { | |
542 $standard = self::realpath($standards[0]); | |
543 if (is_dir($standard) === true | |
544 && is_file(self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true | |
545 ) { | |
546 $standard = self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml'); | |
547 } | |
548 } | |
549 | |
550 $sniffs = $this->_expandRulesetReference($restrictions[0], dirname($standard)); | |
551 } else { | |
552 $sniffs = array(); | |
553 foreach ($standards as $standard) { | |
554 $installed = $this->getInstalledStandardPath($standard); | |
555 if ($installed !== null) { | |
556 $standard = $installed; | |
557 } else { | |
558 $standard = self::realpath($standard); | |
559 if (is_dir($standard) === true | |
560 && is_file(self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true | |
561 ) { | |
562 $standard = self::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml'); | |
563 } | |
564 } | |
565 | |
566 if (PHP_CODESNIFFER_VERBOSITY === 1) { | |
567 $ruleset = simplexml_load_string(file_get_contents($standard)); | |
568 if ($ruleset !== false) { | |
569 $standardName = (string) $ruleset['name']; | |
570 } | |
571 | |
572 echo "Registering sniffs in the $standardName standard... "; | |
573 if (count($standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) { | |
574 echo PHP_EOL; | |
575 } | |
576 } | |
577 | |
578 $sniffs = array_merge($sniffs, $this->processRuleset($standard)); | |
579 }//end foreach | |
580 }//end if | |
581 | |
582 $sniffRestrictions = array(); | |
583 foreach ($restrictions as $sniffCode) { | |
584 $parts = explode('.', strtolower($sniffCode)); | |
585 $sniffRestrictions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff'; | |
586 } | |
587 | |
588 $sniffExclusions = array(); | |
589 foreach ($exclusions as $sniffCode) { | |
590 $parts = explode('.', strtolower($sniffCode)); | |
591 $sniffExclusions[] = $parts[0].'_sniffs_'.$parts[1].'_'.$parts[2].'sniff'; | |
592 } | |
593 | |
594 $this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions); | |
595 $this->populateTokenListeners(); | |
596 | |
597 if (PHP_CODESNIFFER_VERBOSITY === 1) { | |
598 $numSniffs = count($this->sniffs); | |
599 echo "DONE ($numSniffs sniffs registered)".PHP_EOL; | |
600 } | |
601 | |
602 }//end initStandard() | |
603 | |
604 | |
605 /** | |
606 * Processes the files/directories that PHP_CodeSniffer was constructed with. | |
607 * | |
608 * @param string|array $files The files and directories to process. For | |
609 * directories, each sub directory will also | |
610 * be traversed for source files. | |
611 * @param boolean $local If true, don't recurse into directories. | |
612 * | |
613 * @return void | |
614 * @throws PHP_CodeSniffer_Exception If files are invalid. | |
615 */ | |
616 public function processFiles($files, $local=false) | |
617 { | |
618 $files = (array) $files; | |
619 $cliValues = $this->cli->getCommandLineValues(); | |
620 $showProgress = $cliValues['showProgress']; | |
621 $useColors = $cliValues['colors']; | |
622 | |
623 if (PHP_CODESNIFFER_VERBOSITY > 0) { | |
624 echo 'Creating file list... '; | |
625 } | |
626 | |
627 if (empty($this->allowedFileExtensions) === true) { | |
628 $this->allowedFileExtensions = $this->defaultFileExtensions; | |
629 } | |
630 | |
631 $todo = $this->getFilesToProcess($files, $local); | |
632 $numFiles = count($todo); | |
633 | |
634 if (PHP_CODESNIFFER_VERBOSITY > 0) { | |
635 echo "DONE ($numFiles files in queue)".PHP_EOL; | |
636 } | |
637 | |
638 $numProcessed = 0; | |
639 $dots = 0; | |
640 $maxLength = strlen($numFiles); | |
641 $lastDir = ''; | |
642 foreach ($todo as $file) { | |
643 $this->file = $file; | |
644 $currDir = dirname($file); | |
645 if ($lastDir !== $currDir) { | |
646 if (PHP_CODESNIFFER_VERBOSITY > 0 || PHP_CODESNIFFER_CBF === true) { | |
647 echo 'Changing into directory '.$currDir.PHP_EOL; | |
648 } | |
649 | |
650 $lastDir = $currDir; | |
651 } | |
652 | |
653 $phpcsFile = $this->processFile($file, null); | |
654 $numProcessed++; | |
655 | |
656 if (PHP_CODESNIFFER_VERBOSITY > 0 | |
657 || PHP_CODESNIFFER_INTERACTIVE === true | |
658 || $showProgress === false | |
659 ) { | |
660 continue; | |
661 } | |
662 | |
663 // Show progress information. | |
664 if ($phpcsFile === null) { | |
665 echo 'S'; | |
666 } else { | |
667 $errors = $phpcsFile->getErrorCount(); | |
668 $warnings = $phpcsFile->getWarningCount(); | |
669 if ($errors > 0) { | |
670 if ($useColors === true) { | |
671 echo "\033[31m"; | |
672 } | |
673 | |
674 echo 'E'; | |
675 } else if ($warnings > 0) { | |
676 if ($useColors === true) { | |
677 echo "\033[33m"; | |
678 } | |
679 | |
680 echo 'W'; | |
681 } else { | |
682 echo '.'; | |
683 } | |
684 | |
685 if ($useColors === true) { | |
686 echo "\033[0m"; | |
687 } | |
688 }//end if | |
689 | |
690 $dots++; | |
691 if ($dots === 60) { | |
692 $padding = ($maxLength - strlen($numProcessed)); | |
693 echo str_repeat(' ', $padding); | |
694 $percent = round(($numProcessed / $numFiles) * 100); | |
695 echo " $numProcessed / $numFiles ($percent%)".PHP_EOL; | |
696 $dots = 0; | |
697 } | |
698 }//end foreach | |
699 | |
700 if (PHP_CODESNIFFER_VERBOSITY === 0 | |
701 && PHP_CODESNIFFER_INTERACTIVE === false | |
702 && $showProgress === true | |
703 ) { | |
704 echo PHP_EOL.PHP_EOL; | |
705 } | |
706 | |
707 }//end processFiles() | |
708 | |
709 | |
710 /** | |
711 * Processes a single ruleset and returns a list of the sniffs it represents. | |
712 * | |
713 * Rules founds within the ruleset are processed immediately, but sniff classes | |
714 * are not registered by this method. | |
715 * | |
716 * @param string $rulesetPath The path to a ruleset XML file. | |
717 * @param int $depth How many nested processing steps we are in. This | |
718 * is only used for debug output. | |
719 * | |
720 * @return array | |
721 * @throws PHP_CodeSniffer_Exception If the ruleset path is invalid. | |
722 */ | |
723 public function processRuleset($rulesetPath, $depth=0) | |
724 { | |
725 $rulesetPath = self::realpath($rulesetPath); | |
726 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
727 echo str_repeat("\t", $depth); | |
728 echo "Processing ruleset $rulesetPath".PHP_EOL; | |
729 } | |
730 | |
731 $ruleset = simplexml_load_string(file_get_contents($rulesetPath)); | |
732 if ($ruleset === false) { | |
733 throw new PHP_CodeSniffer_Exception("Ruleset $rulesetPath is not valid"); | |
734 } | |
735 | |
736 $ownSniffs = array(); | |
737 $includedSniffs = array(); | |
738 $excludedSniffs = array(); | |
739 $cliValues = $this->cli->getCommandLineValues(); | |
740 | |
741 $rulesetDir = dirname($rulesetPath); | |
742 self::$rulesetDirs[] = $rulesetDir; | |
743 | |
744 if (is_dir($rulesetDir.DIRECTORY_SEPARATOR.'Sniffs') === true) { | |
745 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
746 echo str_repeat("\t", $depth); | |
747 echo "\tAdding sniff files from \"/.../".basename($rulesetDir)."/Sniffs/\" directory".PHP_EOL; | |
748 } | |
749 | |
750 $ownSniffs = $this->_expandSniffDirectory($rulesetDir.DIRECTORY_SEPARATOR.'Sniffs', $depth); | |
751 } | |
752 | |
753 // Process custom sniff config settings. | |
754 foreach ($ruleset->{'config'} as $config) { | |
755 if ($this->_shouldProcessElement($config) === false) { | |
756 continue; | |
757 } | |
758 | |
759 $this->setConfigData((string) $config['name'], (string) $config['value'], true); | |
760 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
761 echo str_repeat("\t", $depth); | |
762 echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL; | |
763 } | |
764 } | |
765 | |
766 foreach ($ruleset->rule as $rule) { | |
767 if (isset($rule['ref']) === false | |
768 || $this->_shouldProcessElement($rule) === false | |
769 ) { | |
770 continue; | |
771 } | |
772 | |
773 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
774 echo str_repeat("\t", $depth); | |
775 echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL; | |
776 } | |
777 | |
778 $includedSniffs = array_merge( | |
779 $includedSniffs, | |
780 $this->_expandRulesetReference($rule['ref'], $rulesetDir, $depth) | |
781 ); | |
782 | |
783 if (isset($rule->exclude) === true) { | |
784 foreach ($rule->exclude as $exclude) { | |
785 if ($this->_shouldProcessElement($exclude) === false) { | |
786 continue; | |
787 } | |
788 | |
789 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
790 echo str_repeat("\t", $depth); | |
791 echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL; | |
792 } | |
793 | |
794 // Check if a single code is being excluded, which is a shortcut | |
795 // for setting the severity of the message to 0. | |
796 $parts = explode('.', $exclude['name']); | |
797 if (count($parts) === 4) { | |
798 $this->ruleset[(string) $exclude['name']]['severity'] = 0; | |
799 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
800 echo str_repeat("\t", $depth); | |
801 echo "\t\t=> severity set to 0".PHP_EOL; | |
802 } | |
803 } else { | |
804 $excludedSniffs = array_merge( | |
805 $excludedSniffs, | |
806 $this->_expandRulesetReference($exclude['name'], $rulesetDir, ($depth + 1)) | |
807 ); | |
808 } | |
809 }//end foreach | |
810 }//end if | |
811 | |
812 $this->_processRule($rule, $depth); | |
813 }//end foreach | |
814 | |
815 // Process custom command line arguments. | |
816 $cliArgs = array(); | |
817 foreach ($ruleset->{'arg'} as $arg) { | |
818 if ($this->_shouldProcessElement($arg) === false) { | |
819 continue; | |
820 } | |
821 | |
822 if (isset($arg['name']) === true) { | |
823 $argString = '--'.(string) $arg['name']; | |
824 if (isset($arg['value']) === true) { | |
825 $argString .= '='.(string) $arg['value']; | |
826 } | |
827 } else { | |
828 $argString = '-'.(string) $arg['value']; | |
829 } | |
830 | |
831 $cliArgs[] = $argString; | |
832 | |
833 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
834 echo str_repeat("\t", $depth); | |
835 echo "\t=> set command line value $argString".PHP_EOL; | |
836 } | |
837 }//end foreach | |
838 | |
839 // Set custom php ini values as CLI args. | |
840 foreach ($ruleset->{'ini'} as $arg) { | |
841 if ($this->_shouldProcessElement($arg) === false) { | |
842 continue; | |
843 } | |
844 | |
845 if (isset($arg['name']) === false) { | |
846 continue; | |
847 } | |
848 | |
849 $name = (string) $arg['name']; | |
850 $argString = $name; | |
851 if (isset($arg['value']) === true) { | |
852 $value = (string) $arg['value']; | |
853 $argString .= "=$value"; | |
854 } else { | |
855 $value = 'true'; | |
856 } | |
857 | |
858 $cliArgs[] = '-d'; | |
859 $cliArgs[] = $argString; | |
860 | |
861 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
862 echo str_repeat("\t", $depth); | |
863 echo "\t=> set PHP ini value $name to $value".PHP_EOL; | |
864 } | |
865 }//end foreach | |
866 | |
867 if (empty($cliValues['files']) === true && $cliValues['stdin'] === null) { | |
868 // Process hard-coded file paths. | |
869 foreach ($ruleset->{'file'} as $file) { | |
870 $file = (string) $file; | |
871 $cliArgs[] = $file; | |
872 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
873 echo str_repeat("\t", $depth); | |
874 echo "\t=> added \"$file\" to the file list".PHP_EOL; | |
875 } | |
876 } | |
877 } | |
878 | |
879 if (empty($cliArgs) === false) { | |
880 // Change the directory so all relative paths are worked | |
881 // out based on the location of the ruleset instead of | |
882 // the location of the user. | |
883 $inPhar = self::isPharFile($rulesetDir); | |
884 if ($inPhar === false) { | |
885 $currentDir = getcwd(); | |
886 chdir($rulesetDir); | |
887 } | |
888 | |
889 $this->cli->setCommandLineValues($cliArgs); | |
890 | |
891 if ($inPhar === false) { | |
892 chdir($currentDir); | |
893 } | |
894 } | |
895 | |
896 // Process custom ignore pattern rules. | |
897 foreach ($ruleset->{'exclude-pattern'} as $pattern) { | |
898 if ($this->_shouldProcessElement($pattern) === false) { | |
899 continue; | |
900 } | |
901 | |
902 if (isset($pattern['type']) === false) { | |
903 $pattern['type'] = 'absolute'; | |
904 } | |
905 | |
906 $this->ignorePatterns[(string) $pattern] = (string) $pattern['type']; | |
907 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
908 echo str_repeat("\t", $depth); | |
909 echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL; | |
910 } | |
911 } | |
912 | |
913 $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs)); | |
914 $excludedSniffs = array_unique($excludedSniffs); | |
915 | |
916 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
917 $included = count($includedSniffs); | |
918 $excluded = count($excludedSniffs); | |
919 echo str_repeat("\t", $depth); | |
920 echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL; | |
921 } | |
922 | |
923 // Merge our own sniff list with our externally included | |
924 // sniff list, but filter out any excluded sniffs. | |
925 $files = array(); | |
926 foreach ($includedSniffs as $sniff) { | |
927 if (in_array($sniff, $excludedSniffs) === true) { | |
928 continue; | |
929 } else { | |
930 $files[] = self::realpath($sniff); | |
931 } | |
932 } | |
933 | |
934 return $files; | |
935 | |
936 }//end processRuleset() | |
937 | |
938 | |
939 /** | |
940 * Expands a directory into a list of sniff files within. | |
941 * | |
942 * @param string $directory The path to a directory. | |
943 * @param int $depth How many nested processing steps we are in. This | |
944 * is only used for debug output. | |
945 * | |
946 * @return array | |
947 */ | |
948 private function _expandSniffDirectory($directory, $depth=0) | |
949 { | |
950 $sniffs = array(); | |
951 | |
952 if (defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') === true) { | |
953 $rdi = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::FOLLOW_SYMLINKS); | |
954 } else { | |
955 $rdi = new RecursiveDirectoryIterator($directory); | |
956 } | |
957 | |
958 $di = new RecursiveIteratorIterator($rdi, 0, RecursiveIteratorIterator::CATCH_GET_CHILD); | |
959 | |
960 $dirLen = strlen($directory); | |
961 | |
962 foreach ($di as $file) { | |
963 $filename = $file->getFilename(); | |
964 | |
965 // Skip hidden files. | |
966 if (substr($filename, 0, 1) === '.') { | |
967 continue; | |
968 } | |
969 | |
970 // We are only interested in PHP and sniff files. | |
971 $fileParts = explode('.', $filename); | |
972 if (array_pop($fileParts) !== 'php') { | |
973 continue; | |
974 } | |
975 | |
976 $basename = basename($filename, '.php'); | |
977 if (substr($basename, -5) !== 'Sniff') { | |
978 continue; | |
979 } | |
980 | |
981 $path = $file->getPathname(); | |
982 | |
983 // Skip files in hidden directories within the Sniffs directory of this | |
984 // standard. We use the offset with strpos() to allow hidden directories | |
985 // before, valid example: | |
986 // /home/foo/.composer/vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/... | |
987 if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) { | |
988 continue; | |
989 } | |
990 | |
991 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
992 echo str_repeat("\t", $depth); | |
993 echo "\t\t=> $path".PHP_EOL; | |
994 } | |
995 | |
996 $sniffs[] = $path; | |
997 }//end foreach | |
998 | |
999 return $sniffs; | |
1000 | |
1001 }//end _expandSniffDirectory() | |
1002 | |
1003 | |
1004 /** | |
1005 * Expands a ruleset reference into a list of sniff files. | |
1006 * | |
1007 * @param string $ref The reference from the ruleset XML file. | |
1008 * @param string $rulesetDir The directory of the ruleset XML file, used to | |
1009 * evaluate relative paths. | |
1010 * @param int $depth How many nested processing steps we are in. This | |
1011 * is only used for debug output. | |
1012 * | |
1013 * @return array | |
1014 * @throws PHP_CodeSniffer_Exception If the reference is invalid. | |
1015 */ | |
1016 private function _expandRulesetReference($ref, $rulesetDir, $depth=0) | |
1017 { | |
1018 // Ignore internal sniffs codes as they are used to only | |
1019 // hide and change internal messages. | |
1020 if (substr($ref, 0, 9) === 'Internal.') { | |
1021 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1022 echo str_repeat("\t", $depth); | |
1023 echo "\t\t* ignoring internal sniff code *".PHP_EOL; | |
1024 } | |
1025 | |
1026 return array(); | |
1027 } | |
1028 | |
1029 // As sniffs can't begin with a full stop, assume references in | |
1030 // this format are relative paths and attempt to convert them | |
1031 // to absolute paths. If this fails, let the reference run through | |
1032 // the normal checks and have it fail as normal. | |
1033 if (substr($ref, 0, 1) === '.') { | |
1034 $realpath = self::realpath($rulesetDir.'/'.$ref); | |
1035 if ($realpath !== false) { | |
1036 $ref = $realpath; | |
1037 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1038 echo str_repeat("\t", $depth); | |
1039 echo "\t\t=> $ref".PHP_EOL; | |
1040 } | |
1041 } | |
1042 } | |
1043 | |
1044 // As sniffs can't begin with a tilde, assume references in | |
1045 // this format at relative to the user's home directory. | |
1046 if (substr($ref, 0, 2) === '~/') { | |
1047 $realpath = self::realpath($ref); | |
1048 if ($realpath !== false) { | |
1049 $ref = $realpath; | |
1050 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1051 echo str_repeat("\t", $depth); | |
1052 echo "\t\t=> $ref".PHP_EOL; | |
1053 } | |
1054 } | |
1055 } | |
1056 | |
1057 if (is_file($ref) === true) { | |
1058 if (substr($ref, -9) === 'Sniff.php') { | |
1059 // A single external sniff. | |
1060 self::$rulesetDirs[] = dirname(dirname(dirname($ref))); | |
1061 return array($ref); | |
1062 } | |
1063 } else { | |
1064 // See if this is a whole standard being referenced. | |
1065 $path = $this->getInstalledStandardPath($ref); | |
1066 if (self::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { | |
1067 // If the ruleset exists inside the phar file, use it. | |
1068 if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { | |
1069 $path = $path.DIRECTORY_SEPARATOR.'ruleset.xml'; | |
1070 } else { | |
1071 $path = null; | |
1072 } | |
1073 } | |
1074 | |
1075 if ($path !== null) { | |
1076 $ref = $path; | |
1077 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1078 echo str_repeat("\t", $depth); | |
1079 echo "\t\t=> $ref".PHP_EOL; | |
1080 } | |
1081 } else if (is_dir($ref) === false) { | |
1082 // Work out the sniff path. | |
1083 $sepPos = strpos($ref, DIRECTORY_SEPARATOR); | |
1084 if ($sepPos !== false) { | |
1085 $stdName = substr($ref, 0, $sepPos); | |
1086 $path = substr($ref, $sepPos); | |
1087 } else { | |
1088 $parts = explode('.', $ref); | |
1089 $stdName = $parts[0]; | |
1090 if (count($parts) === 1) { | |
1091 // A whole standard? | |
1092 $path = ''; | |
1093 } else if (count($parts) === 2) { | |
1094 // A directory of sniffs? | |
1095 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1]; | |
1096 } else { | |
1097 // A single sniff? | |
1098 $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php'; | |
1099 } | |
1100 } | |
1101 | |
1102 $newRef = false; | |
1103 $stdPath = $this->getInstalledStandardPath($stdName); | |
1104 if ($stdPath !== null && $path !== '') { | |
1105 if (self::isPharFile($stdPath) === true | |
1106 && strpos($stdPath, 'ruleset.xml') === false | |
1107 ) { | |
1108 // Phar files can only return the directory, | |
1109 // since ruleset can be omitted if building one standard. | |
1110 $newRef = self::realpath($stdPath.$path); | |
1111 } else { | |
1112 $newRef = self::realpath(dirname($stdPath).$path); | |
1113 } | |
1114 } | |
1115 | |
1116 if ($newRef === false) { | |
1117 // The sniff is not locally installed, so check if it is being | |
1118 // referenced as a remote sniff outside the install. We do this | |
1119 // by looking through all directories where we have found ruleset | |
1120 // files before, looking for ones for this particular standard, | |
1121 // and seeing if it is in there. | |
1122 foreach (self::$rulesetDirs as $dir) { | |
1123 if (strtolower(basename($dir)) !== strtolower($stdName)) { | |
1124 continue; | |
1125 } | |
1126 | |
1127 $newRef = self::realpath($dir.$path); | |
1128 | |
1129 if ($newRef !== false) { | |
1130 $ref = $newRef; | |
1131 } | |
1132 } | |
1133 } else { | |
1134 $ref = $newRef; | |
1135 } | |
1136 | |
1137 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1138 echo str_repeat("\t", $depth); | |
1139 echo "\t\t=> $ref".PHP_EOL; | |
1140 } | |
1141 }//end if | |
1142 }//end if | |
1143 | |
1144 if (is_dir($ref) === true) { | |
1145 if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { | |
1146 // We are referencing an external coding standard. | |
1147 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1148 echo str_repeat("\t", $depth); | |
1149 echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL; | |
1150 } | |
1151 | |
1152 return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2)); | |
1153 } else { | |
1154 // We are referencing a whole directory of sniffs. | |
1155 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1156 echo str_repeat("\t", $depth); | |
1157 echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL; | |
1158 echo str_repeat("\t", $depth); | |
1159 echo "\t\tAdding sniff files from directory".PHP_EOL; | |
1160 } | |
1161 | |
1162 return $this->_expandSniffDirectory($ref, ($depth + 1)); | |
1163 } | |
1164 } else { | |
1165 if (is_file($ref) === false) { | |
1166 $error = "Referenced sniff \"$ref\" does not exist"; | |
1167 throw new PHP_CodeSniffer_Exception($error); | |
1168 } | |
1169 | |
1170 if (substr($ref, -9) === 'Sniff.php') { | |
1171 // A single sniff. | |
1172 return array($ref); | |
1173 } else { | |
1174 // Assume an external ruleset.xml file. | |
1175 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1176 echo str_repeat("\t", $depth); | |
1177 echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL; | |
1178 } | |
1179 | |
1180 return $this->processRuleset($ref, ($depth + 2)); | |
1181 } | |
1182 }//end if | |
1183 | |
1184 }//end _expandRulesetReference() | |
1185 | |
1186 | |
1187 /** | |
1188 * Processes a rule from a ruleset XML file, overriding built-in defaults. | |
1189 * | |
1190 * @param SimpleXMLElement $rule The rule object from a ruleset XML file. | |
1191 * @param int $depth How many nested processing steps we are in. | |
1192 * This is only used for debug output. | |
1193 * | |
1194 * @return void | |
1195 */ | |
1196 private function _processRule($rule, $depth=0) | |
1197 { | |
1198 $code = (string) $rule['ref']; | |
1199 | |
1200 // Custom severity. | |
1201 if (isset($rule->severity) === true | |
1202 && $this->_shouldProcessElement($rule->severity) === true | |
1203 ) { | |
1204 if (isset($this->ruleset[$code]) === false) { | |
1205 $this->ruleset[$code] = array(); | |
1206 } | |
1207 | |
1208 $this->ruleset[$code]['severity'] = (int) $rule->severity; | |
1209 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1210 echo str_repeat("\t", $depth); | |
1211 echo "\t\t=> severity set to ".(int) $rule->severity.PHP_EOL; | |
1212 } | |
1213 } | |
1214 | |
1215 // Custom message type. | |
1216 if (isset($rule->type) === true | |
1217 && $this->_shouldProcessElement($rule->type) === true | |
1218 ) { | |
1219 if (isset($this->ruleset[$code]) === false) { | |
1220 $this->ruleset[$code] = array(); | |
1221 } | |
1222 | |
1223 $this->ruleset[$code]['type'] = (string) $rule->type; | |
1224 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1225 echo str_repeat("\t", $depth); | |
1226 echo "\t\t=> message type set to ".(string) $rule->type.PHP_EOL; | |
1227 } | |
1228 } | |
1229 | |
1230 // Custom message. | |
1231 if (isset($rule->message) === true | |
1232 && $this->_shouldProcessElement($rule->message) === true | |
1233 ) { | |
1234 if (isset($this->ruleset[$code]) === false) { | |
1235 $this->ruleset[$code] = array(); | |
1236 } | |
1237 | |
1238 $this->ruleset[$code]['message'] = (string) $rule->message; | |
1239 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1240 echo str_repeat("\t", $depth); | |
1241 echo "\t\t=> message set to ".(string) $rule->message.PHP_EOL; | |
1242 } | |
1243 } | |
1244 | |
1245 // Custom properties. | |
1246 if (isset($rule->properties) === true | |
1247 && $this->_shouldProcessElement($rule->properties) === true | |
1248 ) { | |
1249 foreach ($rule->properties->property as $prop) { | |
1250 if ($this->_shouldProcessElement($prop) === false) { | |
1251 continue; | |
1252 } | |
1253 | |
1254 if (isset($this->ruleset[$code]) === false) { | |
1255 $this->ruleset[$code] = array( | |
1256 'properties' => array(), | |
1257 ); | |
1258 } else if (isset($this->ruleset[$code]['properties']) === false) { | |
1259 $this->ruleset[$code]['properties'] = array(); | |
1260 } | |
1261 | |
1262 $name = (string) $prop['name']; | |
1263 if (isset($prop['type']) === true | |
1264 && (string) $prop['type'] === 'array' | |
1265 ) { | |
1266 $value = (string) $prop['value']; | |
1267 $values = array(); | |
1268 foreach (explode(',', $value) as $val) { | |
1269 $v = ''; | |
1270 | |
1271 list($k,$v) = explode('=>', $val.'=>'); | |
1272 if ($v !== '') { | |
1273 $values[$k] = $v; | |
1274 } else { | |
1275 $values[] = $k; | |
1276 } | |
1277 } | |
1278 | |
1279 $this->ruleset[$code]['properties'][$name] = $values; | |
1280 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1281 echo str_repeat("\t", $depth); | |
1282 echo "\t\t=> array property \"$name\" set to \"$value\"".PHP_EOL; | |
1283 } | |
1284 } else { | |
1285 $this->ruleset[$code]['properties'][$name] = (string) $prop['value']; | |
1286 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1287 echo str_repeat("\t", $depth); | |
1288 echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"'.PHP_EOL; | |
1289 } | |
1290 }//end if | |
1291 }//end foreach | |
1292 }//end if | |
1293 | |
1294 // Ignore patterns. | |
1295 foreach ($rule->{'exclude-pattern'} as $pattern) { | |
1296 if ($this->_shouldProcessElement($pattern) === false) { | |
1297 continue; | |
1298 } | |
1299 | |
1300 if (isset($this->ignorePatterns[$code]) === false) { | |
1301 $this->ignorePatterns[$code] = array(); | |
1302 } | |
1303 | |
1304 if (isset($pattern['type']) === false) { | |
1305 $pattern['type'] = 'absolute'; | |
1306 } | |
1307 | |
1308 $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type']; | |
1309 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1310 echo str_repeat("\t", $depth); | |
1311 echo "\t\t=> added sniff-specific ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL; | |
1312 } | |
1313 } | |
1314 | |
1315 }//end _processRule() | |
1316 | |
1317 | |
1318 /** | |
1319 * Determine if an element should be processed or ignored. | |
1320 * | |
1321 * @param SimpleXMLElement $element An object from a ruleset XML file. | |
1322 * @param int $depth How many nested processing steps we are in. | |
1323 * This is only used for debug output. | |
1324 * | |
1325 * @return bool | |
1326 */ | |
1327 private function _shouldProcessElement($element, $depth=0) | |
1328 { | |
1329 if (isset($element['phpcbf-only']) === false | |
1330 && isset($element['phpcs-only']) === false | |
1331 ) { | |
1332 // No exceptions are being made. | |
1333 return true; | |
1334 } | |
1335 | |
1336 if (PHP_CODESNIFFER_CBF === true | |
1337 && isset($element['phpcbf-only']) === true | |
1338 && (string) $element['phpcbf-only'] === 'true' | |
1339 ) { | |
1340 return true; | |
1341 } | |
1342 | |
1343 if (PHP_CODESNIFFER_CBF === false | |
1344 && isset($element['phpcs-only']) === true | |
1345 && (string) $element['phpcs-only'] === 'true' | |
1346 ) { | |
1347 return true; | |
1348 } | |
1349 | |
1350 return false; | |
1351 | |
1352 }//end _shouldProcessElement() | |
1353 | |
1354 | |
1355 /** | |
1356 * Loads and stores sniffs objects used for sniffing files. | |
1357 * | |
1358 * @param array $files Paths to the sniff files to register. | |
1359 * @param array $restrictions The sniff class names to restrict the allowed | |
1360 * listeners to. | |
1361 * @param array $exclusions The sniff class names to exclude from the | |
1362 * listeners list. | |
1363 * | |
1364 * @return void | |
1365 * @throws PHP_CodeSniffer_Exception If a sniff file path is invalid. | |
1366 */ | |
1367 public function registerSniffs($files, $restrictions, $exclusions) | |
1368 { | |
1369 $listeners = array(); | |
1370 | |
1371 foreach ($files as $file) { | |
1372 // Work out where the position of /StandardName/Sniffs/... is | |
1373 // so we can determine what the class will be called. | |
1374 $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR); | |
1375 if ($sniffPos === false) { | |
1376 continue; | |
1377 } | |
1378 | |
1379 $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR); | |
1380 if ($slashPos === false) { | |
1381 continue; | |
1382 } | |
1383 | |
1384 $className = substr($file, ($slashPos + 1)); | |
1385 | |
1386 if (substr_count($className, DIRECTORY_SEPARATOR) !== 3) { | |
1387 throw new PHP_CodeSniffer_Exception("Sniff file $className is not valid; sniff files must be located in a .../StandardName/Sniffs/CategoryName/ directory"); | |
1388 } | |
1389 | |
1390 $className = substr($className, 0, -4); | |
1391 $className = str_replace(DIRECTORY_SEPARATOR, '_', $className); | |
1392 | |
1393 // If they have specified a list of sniffs to restrict to, check | |
1394 // to see if this sniff is allowed. | |
1395 if (empty($restrictions) === false | |
1396 && in_array(strtolower($className), $restrictions) === false | |
1397 ) { | |
1398 continue; | |
1399 } | |
1400 | |
1401 // If they have specified a list of sniffs to exclude, check | |
1402 // to see if this sniff is allowed. | |
1403 if (empty($exclusions) === false | |
1404 && in_array(strtolower($className), $exclusions) === true | |
1405 ) { | |
1406 continue; | |
1407 } | |
1408 | |
1409 include_once $file; | |
1410 | |
1411 // Support the use of PHP namespaces. If the class name we included | |
1412 // contains namespace separators instead of underscores, use this as the | |
1413 // class name from now on. | |
1414 $classNameNS = str_replace('_', '\\', $className); | |
1415 if (class_exists($classNameNS, false) === true) { | |
1416 $className = $classNameNS; | |
1417 } | |
1418 | |
1419 // Skip abstract classes. | |
1420 $reflection = new ReflectionClass($className); | |
1421 if ($reflection->isAbstract() === true) { | |
1422 continue; | |
1423 } | |
1424 | |
1425 $listeners[$className] = $className; | |
1426 | |
1427 if (PHP_CODESNIFFER_VERBOSITY > 2) { | |
1428 echo "Registered $className".PHP_EOL; | |
1429 } | |
1430 }//end foreach | |
1431 | |
1432 $this->sniffs = $listeners; | |
1433 | |
1434 }//end registerSniffs() | |
1435 | |
1436 | |
1437 /** | |
1438 * Populates the array of PHP_CodeSniffer_Sniff's for this file. | |
1439 * | |
1440 * @return void | |
1441 * @throws PHP_CodeSniffer_Exception If sniff registration fails. | |
1442 */ | |
1443 public function populateTokenListeners() | |
1444 { | |
1445 // Construct a list of listeners indexed by token being listened for. | |
1446 $this->_tokenListeners = array(); | |
1447 | |
1448 foreach ($this->sniffs as $listenerClass) { | |
1449 // Work out the internal code for this sniff. Detect usage of namespace | |
1450 // separators instead of underscores to support PHP namespaces. | |
1451 if (strstr($listenerClass, '\\') === false) { | |
1452 $parts = explode('_', $listenerClass); | |
1453 } else { | |
1454 $parts = explode('\\', $listenerClass); | |
1455 } | |
1456 | |
1457 $code = $parts[0].'.'.$parts[2].'.'.$parts[3]; | |
1458 $code = substr($code, 0, -5); | |
1459 | |
1460 $this->listeners[$listenerClass] = new $listenerClass(); | |
1461 $this->sniffCodes[$code] = $listenerClass; | |
1462 | |
1463 // Set custom properties. | |
1464 if (isset($this->ruleset[$code]['properties']) === true) { | |
1465 foreach ($this->ruleset[$code]['properties'] as $name => $value) { | |
1466 $this->setSniffProperty($listenerClass, $name, $value); | |
1467 } | |
1468 } | |
1469 | |
1470 $tokenizers = array(); | |
1471 $vars = get_class_vars($listenerClass); | |
1472 if (isset($vars['supportedTokenizers']) === true) { | |
1473 foreach ($vars['supportedTokenizers'] as $tokenizer) { | |
1474 $tokenizers[$tokenizer] = $tokenizer; | |
1475 } | |
1476 } else { | |
1477 $tokenizers = array('PHP' => 'PHP'); | |
1478 } | |
1479 | |
1480 $tokens = $this->listeners[$listenerClass]->register(); | |
1481 if (is_array($tokens) === false) { | |
1482 $msg = "Sniff $listenerClass register() method must return an array"; | |
1483 throw new PHP_CodeSniffer_Exception($msg); | |
1484 } | |
1485 | |
1486 $parts = explode('_', str_replace('\\', '_', $listenerClass)); | |
1487 $listenerSource = $parts[0].'.'.$parts[2].'.'.substr($parts[3], 0, -5); | |
1488 $ignorePatterns = array(); | |
1489 $patterns = $this->getIgnorePatterns($listenerSource); | |
1490 foreach ($patterns as $pattern => $type) { | |
1491 // While there is support for a type of each pattern | |
1492 // (absolute or relative) we don't actually support it here. | |
1493 $replacements = array( | |
1494 '\\,' => ',', | |
1495 '*' => '.*', | |
1496 ); | |
1497 | |
1498 $ignorePatterns[] = strtr($pattern, $replacements); | |
1499 } | |
1500 | |
1501 foreach ($tokens as $token) { | |
1502 if (isset($this->_tokenListeners[$token]) === false) { | |
1503 $this->_tokenListeners[$token] = array(); | |
1504 } | |
1505 | |
1506 if (isset($this->_tokenListeners[$token][$listenerClass]) === false) { | |
1507 $this->_tokenListeners[$token][$listenerClass] = array( | |
1508 'class' => $listenerClass, | |
1509 'source' => $listenerSource, | |
1510 'tokenizers' => $tokenizers, | |
1511 'ignore' => $ignorePatterns, | |
1512 ); | |
1513 } | |
1514 } | |
1515 }//end foreach | |
1516 | |
1517 }//end populateTokenListeners() | |
1518 | |
1519 | |
1520 /** | |
1521 * Set a single property for a sniff. | |
1522 * | |
1523 * @param string $listenerClass The class name of the sniff. | |
1524 * @param string $name The name of the property to change. | |
1525 * @param string $value The new value of the property. | |
1526 * | |
1527 * @return void | |
1528 */ | |
1529 public function setSniffProperty($listenerClass, $name, $value) | |
1530 { | |
1531 // Setting a property for a sniff we are not using. | |
1532 if (isset($this->listeners[$listenerClass]) === false) { | |
1533 return; | |
1534 } | |
1535 | |
1536 $name = trim($name); | |
1537 if (is_string($value) === true) { | |
1538 $value = trim($value); | |
1539 } | |
1540 | |
1541 // Special case for booleans. | |
1542 if ($value === 'true') { | |
1543 $value = true; | |
1544 } else if ($value === 'false') { | |
1545 $value = false; | |
1546 } | |
1547 | |
1548 $this->listeners[$listenerClass]->$name = $value; | |
1549 | |
1550 }//end setSniffProperty() | |
1551 | |
1552 | |
1553 /** | |
1554 * Get a list of files that will be processed. | |
1555 * | |
1556 * If passed directories, this method will find all files within them. | |
1557 * The method will also perform file extension and ignore pattern filtering. | |
1558 * | |
1559 * @param string $paths A list of file or directory paths to process. | |
1560 * @param boolean $local If true, only process 1 level of files in directories | |
1561 * | |
1562 * @return array | |
1563 * @throws Exception If there was an error opening a directory. | |
1564 * @see shouldProcessFile() | |
1565 */ | |
1566 public function getFilesToProcess($paths, $local=false) | |
1567 { | |
1568 $files = array(); | |
1569 | |
1570 foreach ($paths as $path) { | |
1571 if (is_dir($path) === true || self::isPharFile($path) === true) { | |
1572 if (self::isPharFile($path) === true) { | |
1573 $path = 'phar://'.$path; | |
1574 } | |
1575 | |
1576 if ($local === true) { | |
1577 $di = new DirectoryIterator($path); | |
1578 } else { | |
1579 $di = new RecursiveIteratorIterator( | |
1580 new RecursiveDirectoryIterator($path), | |
1581 0, | |
1582 RecursiveIteratorIterator::CATCH_GET_CHILD | |
1583 ); | |
1584 } | |
1585 | |
1586 foreach ($di as $file) { | |
1587 // Check if the file exists after all symlinks are resolved. | |
1588 $filePath = self::realpath($file->getPathname()); | |
1589 if ($filePath === false) { | |
1590 continue; | |
1591 } | |
1592 | |
1593 if (is_dir($filePath) === true) { | |
1594 continue; | |
1595 } | |
1596 | |
1597 if ($this->shouldProcessFile($file->getPathname(), $path) === false) { | |
1598 continue; | |
1599 } | |
1600 | |
1601 $files[] = $file->getPathname(); | |
1602 }//end foreach | |
1603 } else { | |
1604 if ($this->shouldIgnoreFile($path, dirname($path)) === true) { | |
1605 continue; | |
1606 } | |
1607 | |
1608 $files[] = $path; | |
1609 }//end if | |
1610 }//end foreach | |
1611 | |
1612 return $files; | |
1613 | |
1614 }//end getFilesToProcess() | |
1615 | |
1616 | |
1617 /** | |
1618 * Checks filtering rules to see if a file should be checked. | |
1619 * | |
1620 * Checks both file extension filters and path ignore filters. | |
1621 * | |
1622 * @param string $path The path to the file being checked. | |
1623 * @param string $basedir The directory to use for relative path checks. | |
1624 * | |
1625 * @return bool | |
1626 */ | |
1627 public function shouldProcessFile($path, $basedir) | |
1628 { | |
1629 // Check that the file's extension is one we are checking. | |
1630 // We are strict about checking the extension and we don't | |
1631 // let files through with no extension or that start with a dot. | |
1632 $fileName = basename($path); | |
1633 $fileParts = explode('.', $fileName); | |
1634 if ($fileParts[0] === $fileName || $fileParts[0] === '') { | |
1635 return false; | |
1636 } | |
1637 | |
1638 // Checking multi-part file extensions, so need to create a | |
1639 // complete extension list and make sure one is allowed. | |
1640 $extensions = array(); | |
1641 array_shift($fileParts); | |
1642 foreach ($fileParts as $part) { | |
1643 $extensions[implode('.', $fileParts)] = 1; | |
1644 array_shift($fileParts); | |
1645 } | |
1646 | |
1647 $matches = array_intersect_key($extensions, $this->allowedFileExtensions); | |
1648 if (empty($matches) === true) { | |
1649 return false; | |
1650 } | |
1651 | |
1652 // If the file's path matches one of our ignore patterns, skip it. | |
1653 if ($this->shouldIgnoreFile($path, $basedir) === true) { | |
1654 return false; | |
1655 } | |
1656 | |
1657 return true; | |
1658 | |
1659 }//end shouldProcessFile() | |
1660 | |
1661 | |
1662 /** | |
1663 * Checks filtering rules to see if a file should be ignored. | |
1664 * | |
1665 * @param string $path The path to the file being checked. | |
1666 * @param string $basedir The directory to use for relative path checks. | |
1667 * | |
1668 * @return bool | |
1669 */ | |
1670 public function shouldIgnoreFile($path, $basedir) | |
1671 { | |
1672 $relativePath = $path; | |
1673 if (strpos($path, $basedir) === 0) { | |
1674 // The +1 cuts off the directory separator as well. | |
1675 $relativePath = substr($path, (strlen($basedir) + 1)); | |
1676 } | |
1677 | |
1678 foreach ($this->ignorePatterns as $pattern => $type) { | |
1679 if (is_array($type) === true) { | |
1680 // A sniff specific ignore pattern. | |
1681 continue; | |
1682 } | |
1683 | |
1684 // Maintains backwards compatibility in case the ignore pattern does | |
1685 // not have a relative/absolute value. | |
1686 if (is_int($pattern) === true) { | |
1687 $pattern = $type; | |
1688 $type = 'absolute'; | |
1689 } | |
1690 | |
1691 $replacements = array( | |
1692 '\\,' => ',', | |
1693 '*' => '.*', | |
1694 ); | |
1695 | |
1696 // We assume a / directory separator, as do the exclude rules | |
1697 // most developers write, so we need a special case for any system | |
1698 // that is different. | |
1699 if (DIRECTORY_SEPARATOR === '\\') { | |
1700 $replacements['/'] = '\\\\'; | |
1701 } | |
1702 | |
1703 $pattern = strtr($pattern, $replacements); | |
1704 | |
1705 if ($type === 'relative') { | |
1706 $testPath = $relativePath; | |
1707 } else { | |
1708 $testPath = $path; | |
1709 } | |
1710 | |
1711 $pattern = '`'.$pattern.'`i'; | |
1712 if (preg_match($pattern, $testPath) === 1) { | |
1713 return true; | |
1714 } | |
1715 }//end foreach | |
1716 | |
1717 return false; | |
1718 | |
1719 }//end shouldIgnoreFile() | |
1720 | |
1721 | |
1722 /** | |
1723 * Run the code sniffs over a single given file. | |
1724 * | |
1725 * Processes the file and runs the PHP_CodeSniffer sniffs to verify that it | |
1726 * conforms with the standard. Returns the processed file object, or NULL | |
1727 * if no file was processed due to error. | |
1728 * | |
1729 * @param string $file The file to process. | |
1730 * @param string $contents The contents to parse. If NULL, the content | |
1731 * is taken from the file system. | |
1732 * | |
1733 * @return PHP_CodeSniffer_File | |
1734 * @throws PHP_CodeSniffer_Exception If the file could not be processed. | |
1735 * @see _processFile() | |
1736 */ | |
1737 public function processFile($file, $contents=null) | |
1738 { | |
1739 if ($contents === null && file_exists($file) === false) { | |
1740 throw new PHP_CodeSniffer_Exception("Source file $file does not exist"); | |
1741 } | |
1742 | |
1743 $filePath = self::realpath($file); | |
1744 if ($filePath === false) { | |
1745 $filePath = $file; | |
1746 } | |
1747 | |
1748 // Before we go and spend time tokenizing this file, just check | |
1749 // to see if there is a tag up top to indicate that the whole | |
1750 // file should be ignored. It must be on one of the first two lines. | |
1751 $firstContent = $contents; | |
1752 if ($contents === null && is_readable($filePath) === true) { | |
1753 $handle = fopen($filePath, 'r'); | |
1754 stream_set_blocking($handle, true); | |
1755 if ($handle !== false) { | |
1756 $firstContent = fgets($handle); | |
1757 $firstContent .= fgets($handle); | |
1758 fclose($handle); | |
1759 | |
1760 if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false) { | |
1761 // We are ignoring the whole file. | |
1762 if (PHP_CODESNIFFER_VERBOSITY > 0) { | |
1763 echo 'Ignoring '.basename($filePath).PHP_EOL; | |
1764 } | |
1765 | |
1766 return null; | |
1767 } | |
1768 } | |
1769 }//end if | |
1770 | |
1771 try { | |
1772 $phpcsFile = $this->_processFile($file, $contents); | |
1773 } catch (Exception $e) { | |
1774 $trace = $e->getTrace(); | |
1775 | |
1776 $filename = $trace[0]['args'][0]; | |
1777 if (is_object($filename) === true | |
1778 && get_class($filename) === 'PHP_CodeSniffer_File' | |
1779 ) { | |
1780 $filename = $filename->getFilename(); | |
1781 } else if (is_numeric($filename) === true) { | |
1782 // See if we can find the PHP_CodeSniffer_File object. | |
1783 foreach ($trace as $data) { | |
1784 if (isset($data['args'][0]) === true | |
1785 && ($data['args'][0] instanceof PHP_CodeSniffer_File) === true | |
1786 ) { | |
1787 $filename = $data['args'][0]->getFilename(); | |
1788 } | |
1789 } | |
1790 } else if (is_string($filename) === false) { | |
1791 $filename = (string) $filename; | |
1792 } | |
1793 | |
1794 $errorMessage = '"'.$e->getMessage().'" at '.$e->getFile().':'.$e->getLine(); | |
1795 $error = "An error occurred during processing; checking has been aborted. The error message was: $errorMessage"; | |
1796 | |
1797 $phpcsFile = new PHP_CodeSniffer_File( | |
1798 $filename, | |
1799 $this->_tokenListeners, | |
1800 $this->ruleset, | |
1801 $this | |
1802 ); | |
1803 | |
1804 $phpcsFile->addError($error, null); | |
1805 }//end try | |
1806 | |
1807 $cliValues = $this->cli->getCommandLineValues(); | |
1808 | |
1809 if (PHP_CODESNIFFER_INTERACTIVE === false) { | |
1810 // Cache the report data for this file so we can unset it to save memory. | |
1811 $this->reporting->cacheFileReport($phpcsFile, $cliValues); | |
1812 $phpcsFile->cleanUp(); | |
1813 return $phpcsFile; | |
1814 } | |
1815 | |
1816 /* | |
1817 Running interactively. | |
1818 Print the error report for the current file and then wait for user input. | |
1819 */ | |
1820 | |
1821 // Get current violations and then clear the list to make sure | |
1822 // we only print violations for a single file each time. | |
1823 $numErrors = null; | |
1824 while ($numErrors !== 0) { | |
1825 $numErrors = ($phpcsFile->getErrorCount() + $phpcsFile->getWarningCount()); | |
1826 if ($numErrors === 0) { | |
1827 continue; | |
1828 } | |
1829 | |
1830 $reportClass = $this->reporting->factory('full'); | |
1831 $reportData = $this->reporting->prepareFileReport($phpcsFile); | |
1832 $reportClass->generateFileReport($reportData, $phpcsFile, $cliValues['showSources'], $cliValues['reportWidth']); | |
1833 | |
1834 echo '<ENTER> to recheck, [s] to skip or [q] to quit : '; | |
1835 $input = fgets(STDIN); | |
1836 $input = trim($input); | |
1837 | |
1838 switch ($input) { | |
1839 case 's': | |
1840 break(2); | |
1841 case 'q': | |
1842 exit(0); | |
1843 break; | |
1844 default: | |
1845 // Repopulate the sniffs because some of them save their state | |
1846 // and only clear it when the file changes, but we are rechecking | |
1847 // the same file. | |
1848 $this->populateTokenListeners(); | |
1849 $phpcsFile = $this->_processFile($file, $contents); | |
1850 break; | |
1851 } | |
1852 }//end while | |
1853 | |
1854 return $phpcsFile; | |
1855 | |
1856 }//end processFile() | |
1857 | |
1858 | |
1859 /** | |
1860 * Process the sniffs for a single file. | |
1861 * | |
1862 * Does raw processing only. No interactive support or error checking. | |
1863 * | |
1864 * @param string $file The file to process. | |
1865 * @param string $contents The contents to parse. If NULL, the content | |
1866 * is taken from the file system. | |
1867 * | |
1868 * @return PHP_CodeSniffer_File | |
1869 * @see processFile() | |
1870 */ | |
1871 private function _processFile($file, $contents) | |
1872 { | |
1873 $stdin = false; | |
1874 $cliValues = $this->cli->getCommandLineValues(); | |
1875 if (empty($cliValues['files']) === true) { | |
1876 $stdin = true; | |
1877 } | |
1878 | |
1879 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) { | |
1880 $startTime = microtime(true); | |
1881 echo 'Processing '.basename($file).' '; | |
1882 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
1883 echo PHP_EOL; | |
1884 } | |
1885 } | |
1886 | |
1887 $phpcsFile = new PHP_CodeSniffer_File( | |
1888 $file, | |
1889 $this->_tokenListeners, | |
1890 $this->ruleset, | |
1891 $this | |
1892 ); | |
1893 | |
1894 $phpcsFile->start($contents); | |
1895 | |
1896 if (PHP_CODESNIFFER_VERBOSITY > 0 || (PHP_CODESNIFFER_CBF === true && $stdin === false)) { | |
1897 $timeTaken = ((microtime(true) - $startTime) * 1000); | |
1898 if ($timeTaken < 1000) { | |
1899 $timeTaken = round($timeTaken); | |
1900 echo "DONE in {$timeTaken}ms"; | |
1901 } else { | |
1902 $timeTaken = round(($timeTaken / 1000), 2); | |
1903 echo "DONE in $timeTaken secs"; | |
1904 } | |
1905 | |
1906 if (PHP_CODESNIFFER_CBF === true) { | |
1907 $errors = $phpcsFile->getFixableCount(); | |
1908 echo " ($errors fixable violations)".PHP_EOL; | |
1909 } else { | |
1910 $errors = $phpcsFile->getErrorCount(); | |
1911 $warnings = $phpcsFile->getWarningCount(); | |
1912 echo " ($errors errors, $warnings warnings)".PHP_EOL; | |
1913 } | |
1914 } | |
1915 | |
1916 return $phpcsFile; | |
1917 | |
1918 }//end _processFile() | |
1919 | |
1920 | |
1921 /** | |
1922 * Generates documentation for a coding standard. | |
1923 * | |
1924 * @param string $standard The standard to generate docs for | |
1925 * @param array $sniffs A list of sniffs to limit the docs to. | |
1926 * @param string $generator The name of the generator class to use. | |
1927 * | |
1928 * @return void | |
1929 */ | |
1930 public function generateDocs($standard, array $sniffs=array(), $generator='Text') | |
1931 { | |
1932 if (class_exists('PHP_CodeSniffer_DocGenerators_'.$generator, true) === false) { | |
1933 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_DocGenerators_'.$generator.' not found'); | |
1934 } | |
1935 | |
1936 $class = "PHP_CodeSniffer_DocGenerators_$generator"; | |
1937 $generator = new $class($standard, $sniffs); | |
1938 | |
1939 $generator->generate(); | |
1940 | |
1941 }//end generateDocs() | |
1942 | |
1943 | |
1944 /** | |
1945 * Gets the array of PHP_CodeSniffer_Sniff's. | |
1946 * | |
1947 * @return PHP_CodeSniffer_Sniff[] | |
1948 */ | |
1949 public function getSniffs() | |
1950 { | |
1951 return $this->listeners; | |
1952 | |
1953 }//end getSniffs() | |
1954 | |
1955 | |
1956 /** | |
1957 * Gets the array of PHP_CodeSniffer_Sniff's indexed by token type. | |
1958 * | |
1959 * @return array | |
1960 */ | |
1961 public function getTokenSniffs() | |
1962 { | |
1963 return $this->_tokenListeners; | |
1964 | |
1965 }//end getTokenSniffs() | |
1966 | |
1967 | |
1968 /** | |
1969 * Returns true if the specified string is in the camel caps format. | |
1970 * | |
1971 * @param string $string The string the verify. | |
1972 * @param boolean $classFormat If true, check to see if the string is in the | |
1973 * class format. Class format strings must start | |
1974 * with a capital letter and contain no | |
1975 * underscores. | |
1976 * @param boolean $public If true, the first character in the string | |
1977 * must be an a-z character. If false, the | |
1978 * character must be an underscore. This | |
1979 * argument is only applicable if $classFormat | |
1980 * is false. | |
1981 * @param boolean $strict If true, the string must not have two capital | |
1982 * letters next to each other. If false, a | |
1983 * relaxed camel caps policy is used to allow | |
1984 * for acronyms. | |
1985 * | |
1986 * @return boolean | |
1987 */ | |
1988 public static function isCamelCaps( | |
1989 $string, | |
1990 $classFormat=false, | |
1991 $public=true, | |
1992 $strict=true | |
1993 ) { | |
1994 // Check the first character first. | |
1995 if ($classFormat === false) { | |
1996 $legalFirstChar = ''; | |
1997 if ($public === false) { | |
1998 $legalFirstChar = '[_]'; | |
1999 } | |
2000 | |
2001 if ($strict === false) { | |
2002 // Can either start with a lowercase letter, or multiple uppercase | |
2003 // in a row, representing an acronym. | |
2004 $legalFirstChar .= '([A-Z]{2,}|[a-z])'; | |
2005 } else { | |
2006 $legalFirstChar .= '[a-z]'; | |
2007 } | |
2008 } else { | |
2009 $legalFirstChar = '[A-Z]'; | |
2010 } | |
2011 | |
2012 if (preg_match("/^$legalFirstChar/", $string) === 0) { | |
2013 return false; | |
2014 } | |
2015 | |
2016 // Check that the name only contains legal characters. | |
2017 $legalChars = 'a-zA-Z0-9'; | |
2018 if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) { | |
2019 return false; | |
2020 } | |
2021 | |
2022 if ($strict === true) { | |
2023 // Check that there are not two capital letters next to each other. | |
2024 $length = strlen($string); | |
2025 $lastCharWasCaps = $classFormat; | |
2026 | |
2027 for ($i = 1; $i < $length; $i++) { | |
2028 $ascii = ord($string{$i}); | |
2029 if ($ascii >= 48 && $ascii <= 57) { | |
2030 // The character is a number, so it cant be a capital. | |
2031 $isCaps = false; | |
2032 } else { | |
2033 if (strtoupper($string{$i}) === $string{$i}) { | |
2034 $isCaps = true; | |
2035 } else { | |
2036 $isCaps = false; | |
2037 } | |
2038 } | |
2039 | |
2040 if ($isCaps === true && $lastCharWasCaps === true) { | |
2041 return false; | |
2042 } | |
2043 | |
2044 $lastCharWasCaps = $isCaps; | |
2045 } | |
2046 }//end if | |
2047 | |
2048 return true; | |
2049 | |
2050 }//end isCamelCaps() | |
2051 | |
2052 | |
2053 /** | |
2054 * Returns true if the specified string is in the underscore caps format. | |
2055 * | |
2056 * @param string $string The string to verify. | |
2057 * | |
2058 * @return boolean | |
2059 */ | |
2060 public static function isUnderscoreName($string) | |
2061 { | |
2062 // If there are space in the name, it can't be valid. | |
2063 if (strpos($string, ' ') !== false) { | |
2064 return false; | |
2065 } | |
2066 | |
2067 $validName = true; | |
2068 $nameBits = explode('_', $string); | |
2069 | |
2070 if (preg_match('|^[A-Z]|', $string) === 0) { | |
2071 // Name does not begin with a capital letter. | |
2072 $validName = false; | |
2073 } else { | |
2074 foreach ($nameBits as $bit) { | |
2075 if ($bit === '') { | |
2076 continue; | |
2077 } | |
2078 | |
2079 if ($bit{0} !== strtoupper($bit{0})) { | |
2080 $validName = false; | |
2081 break; | |
2082 } | |
2083 } | |
2084 } | |
2085 | |
2086 return $validName; | |
2087 | |
2088 }//end isUnderscoreName() | |
2089 | |
2090 | |
2091 /** | |
2092 * Returns a valid variable type for param/var tag. | |
2093 * | |
2094 * If type is not one of the standard type, it must be a custom type. | |
2095 * Returns the correct type name suggestion if type name is invalid. | |
2096 * | |
2097 * @param string $varType The variable type to process. | |
2098 * | |
2099 * @return string | |
2100 */ | |
2101 public static function suggestType($varType) | |
2102 { | |
2103 if ($varType === '') { | |
2104 return ''; | |
2105 } | |
2106 | |
2107 if (in_array($varType, self::$allowedTypes) === true) { | |
2108 return $varType; | |
2109 } else { | |
2110 $lowerVarType = strtolower($varType); | |
2111 switch ($lowerVarType) { | |
2112 case 'bool': | |
2113 case 'boolean': | |
2114 return 'boolean'; | |
2115 case 'double': | |
2116 case 'real': | |
2117 case 'float': | |
2118 return 'float'; | |
2119 case 'int': | |
2120 case 'integer': | |
2121 return 'integer'; | |
2122 case 'array()': | |
2123 case 'array': | |
2124 return 'array'; | |
2125 }//end switch | |
2126 | |
2127 if (strpos($lowerVarType, 'array(') !== false) { | |
2128 // Valid array declaration: | |
2129 // array, array(type), array(type1 => type2). | |
2130 $matches = array(); | |
2131 $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i'; | |
2132 if (preg_match($pattern, $varType, $matches) !== 0) { | |
2133 $type1 = ''; | |
2134 if (isset($matches[1]) === true) { | |
2135 $type1 = $matches[1]; | |
2136 } | |
2137 | |
2138 $type2 = ''; | |
2139 if (isset($matches[3]) === true) { | |
2140 $type2 = $matches[3]; | |
2141 } | |
2142 | |
2143 $type1 = self::suggestType($type1); | |
2144 $type2 = self::suggestType($type2); | |
2145 if ($type2 !== '') { | |
2146 $type2 = ' => '.$type2; | |
2147 } | |
2148 | |
2149 return "array($type1$type2)"; | |
2150 } else { | |
2151 return 'array'; | |
2152 }//end if | |
2153 } else if (in_array($lowerVarType, self::$allowedTypes) === true) { | |
2154 // A valid type, but not lower cased. | |
2155 return $lowerVarType; | |
2156 } else { | |
2157 // Must be a custom type name. | |
2158 return $varType; | |
2159 }//end if | |
2160 }//end if | |
2161 | |
2162 }//end suggestType() | |
2163 | |
2164 | |
2165 /** | |
2166 * Prepares token content for output to screen. | |
2167 * | |
2168 * Replaces invisible characters so they are visible. On non-Windows | |
2169 * OSes it will also colour the invisible characters. | |
2170 * | |
2171 * @param string $content The content to prepare. | |
2172 * | |
2173 * @return string | |
2174 */ | |
2175 public static function prepareForOutput($content) | |
2176 { | |
2177 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { | |
2178 $content = str_replace("\r", '\r', $content); | |
2179 $content = str_replace("\n", '\n', $content); | |
2180 $content = str_replace("\t", '\t', $content); | |
2181 } else { | |
2182 $content = str_replace("\r", "\033[30;1m\\r\033[0m", $content); | |
2183 $content = str_replace("\n", "\033[30;1m\\n\033[0m", $content); | |
2184 $content = str_replace("\t", "\033[30;1m\\t\033[0m", $content); | |
2185 $content = str_replace(' ', "\033[30;1m·\033[0m", $content); | |
2186 } | |
2187 | |
2188 return $content; | |
2189 | |
2190 }//end prepareForOutput() | |
2191 | |
2192 | |
2193 /** | |
2194 * Get a list paths where standards are installed. | |
2195 * | |
2196 * @return array | |
2197 */ | |
2198 public static function getInstalledStandardPaths() | |
2199 { | |
2200 $installedPaths = array(dirname(__FILE__).DIRECTORY_SEPARATOR.'CodeSniffer'.DIRECTORY_SEPARATOR.'Standards'); | |
2201 $configPaths = PHP_CodeSniffer::getConfigData('installed_paths'); | |
2202 if ($configPaths !== null) { | |
2203 $installedPaths = array_merge($installedPaths, explode(',', $configPaths)); | |
2204 } | |
2205 | |
2206 $resolvedInstalledPaths = array(); | |
2207 foreach ($installedPaths as $installedPath) { | |
2208 if (substr($installedPath, 0, 1) === '.') { | |
2209 $installedPath = dirname(__FILE__).DIRECTORY_SEPARATOR.$installedPath; | |
2210 } | |
2211 | |
2212 $resolvedInstalledPaths[] = $installedPath; | |
2213 } | |
2214 | |
2215 return $resolvedInstalledPaths; | |
2216 | |
2217 }//end getInstalledStandardPaths() | |
2218 | |
2219 | |
2220 /** | |
2221 * Get a list of all coding standards installed. | |
2222 * | |
2223 * Coding standards are directories located in the | |
2224 * CodeSniffer/Standards directory. Valid coding standards | |
2225 * include a Sniffs subdirectory. | |
2226 * | |
2227 * @param boolean $includeGeneric If true, the special "Generic" | |
2228 * coding standard will be included | |
2229 * if installed. | |
2230 * @param string $standardsDir A specific directory to look for standards | |
2231 * in. If not specified, PHP_CodeSniffer will | |
2232 * look in its default locations. | |
2233 * | |
2234 * @return array | |
2235 * @see isInstalledStandard() | |
2236 */ | |
2237 public static function getInstalledStandards( | |
2238 $includeGeneric=false, | |
2239 $standardsDir='' | |
2240 ) { | |
2241 $installedStandards = array(); | |
2242 | |
2243 if ($standardsDir === '') { | |
2244 $installedPaths = self::getInstalledStandardPaths(); | |
2245 } else { | |
2246 $installedPaths = array($standardsDir); | |
2247 } | |
2248 | |
2249 foreach ($installedPaths as $standardsDir) { | |
2250 $di = new DirectoryIterator($standardsDir); | |
2251 foreach ($di as $file) { | |
2252 if ($file->isDir() === true && $file->isDot() === false) { | |
2253 $filename = $file->getFilename(); | |
2254 | |
2255 // Ignore the special "Generic" standard. | |
2256 if ($includeGeneric === false && $filename === 'Generic') { | |
2257 continue; | |
2258 } | |
2259 | |
2260 // Valid coding standard dirs include a ruleset. | |
2261 $csFile = $file->getPathname().'/ruleset.xml'; | |
2262 if (is_file($csFile) === true) { | |
2263 $installedStandards[] = $filename; | |
2264 } | |
2265 } | |
2266 } | |
2267 }//end foreach | |
2268 | |
2269 return $installedStandards; | |
2270 | |
2271 }//end getInstalledStandards() | |
2272 | |
2273 | |
2274 /** | |
2275 * Determine if a standard is installed. | |
2276 * | |
2277 * Coding standards are directories located in the | |
2278 * CodeSniffer/Standards directory. Valid coding standards | |
2279 * include a ruleset.xml file. | |
2280 * | |
2281 * @param string $standard The name of the coding standard. | |
2282 * | |
2283 * @return boolean | |
2284 * @see getInstalledStandards() | |
2285 */ | |
2286 public static function isInstalledStandard($standard) | |
2287 { | |
2288 $path = self::getInstalledStandardPath($standard); | |
2289 if ($path !== null && strpos($path, 'ruleset.xml') !== false) { | |
2290 return true; | |
2291 } else { | |
2292 // This could be a custom standard, installed outside our | |
2293 // standards directory. | |
2294 $standard = self::realPath($standard); | |
2295 | |
2296 // Might be an actual ruleset file itself. | |
2297 // If it has an XML extension, let's at least try it. | |
2298 if (is_file($standard) === true | |
2299 && (substr(strtolower($standard), -4) === '.xml' | |
2300 || substr(strtolower($standard), -9) === '.xml.dist') | |
2301 ) { | |
2302 return true; | |
2303 } | |
2304 | |
2305 // If it is a directory with a ruleset.xml file in it, | |
2306 // it is a standard. | |
2307 $ruleset = rtrim($standard, ' /\\').DIRECTORY_SEPARATOR.'ruleset.xml'; | |
2308 if (is_file($ruleset) === true) { | |
2309 return true; | |
2310 } | |
2311 }//end if | |
2312 | |
2313 return false; | |
2314 | |
2315 }//end isInstalledStandard() | |
2316 | |
2317 | |
2318 /** | |
2319 * Return the path of an installed coding standard. | |
2320 * | |
2321 * Coding standards are directories located in the | |
2322 * CodeSniffer/Standards directory. Valid coding standards | |
2323 * include a ruleset.xml file. | |
2324 * | |
2325 * @param string $standard The name of the coding standard. | |
2326 * | |
2327 * @return string|null | |
2328 */ | |
2329 public static function getInstalledStandardPath($standard) | |
2330 { | |
2331 $installedPaths = self::getInstalledStandardPaths(); | |
2332 foreach ($installedPaths as $installedPath) { | |
2333 $standardPath = $installedPath.DIRECTORY_SEPARATOR.$standard; | |
2334 $path = self::realpath($standardPath.DIRECTORY_SEPARATOR.'ruleset.xml'); | |
2335 if (is_file($path) === true) { | |
2336 return $path; | |
2337 } else if (self::isPharFile($standardPath) === true) { | |
2338 $path = self::realpath($standardPath); | |
2339 if ($path !== false) { | |
2340 return $path; | |
2341 } | |
2342 } | |
2343 } | |
2344 | |
2345 return null; | |
2346 | |
2347 }//end getInstalledStandardPath() | |
2348 | |
2349 | |
2350 /** | |
2351 * Get a single config value. | |
2352 * | |
2353 * Config data is stored in the data dir, in a file called | |
2354 * CodeSniffer.conf. It is a simple PHP array. | |
2355 * | |
2356 * @param string $key The name of the config value. | |
2357 * | |
2358 * @return string|null | |
2359 * @see setConfigData() | |
2360 * @see getAllConfigData() | |
2361 */ | |
2362 public static function getConfigData($key) | |
2363 { | |
2364 $phpCodeSnifferConfig = self::getAllConfigData(); | |
2365 | |
2366 if ($phpCodeSnifferConfig === null) { | |
2367 return null; | |
2368 } | |
2369 | |
2370 if (isset($phpCodeSnifferConfig[$key]) === false) { | |
2371 return null; | |
2372 } | |
2373 | |
2374 return $phpCodeSnifferConfig[$key]; | |
2375 | |
2376 }//end getConfigData() | |
2377 | |
2378 | |
2379 /** | |
2380 * Set a single config value. | |
2381 * | |
2382 * Config data is stored in the data dir, in a file called | |
2383 * CodeSniffer.conf. It is a simple PHP array. | |
2384 * | |
2385 * @param string $key The name of the config value. | |
2386 * @param string|null $value The value to set. If null, the config | |
2387 * entry is deleted, reverting it to the | |
2388 * default value. | |
2389 * @param boolean $temp Set this config data temporarily for this | |
2390 * script run. This will not write the config | |
2391 * data to the config file. | |
2392 * | |
2393 * @return boolean | |
2394 * @see getConfigData() | |
2395 * @throws PHP_CodeSniffer_Exception If the config file can not be written. | |
2396 */ | |
2397 public static function setConfigData($key, $value, $temp=false) | |
2398 { | |
2399 if ($temp === false) { | |
2400 $path = ''; | |
2401 if (is_callable('Phar::running') === true) { | |
2402 $path = Phar::running(false); | |
2403 } | |
2404 | |
2405 if ($path !== '') { | |
2406 $configFile = dirname($path).'/CodeSniffer.conf'; | |
2407 } else { | |
2408 $configFile = dirname(__FILE__).'/CodeSniffer.conf'; | |
2409 if (is_file($configFile) === false | |
2410 && strpos('@data_dir@', '@data_dir') === false | |
2411 ) { | |
2412 // If data_dir was replaced, this is a PEAR install and we can | |
2413 // use the PEAR data dir to store the conf file. | |
2414 $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; | |
2415 } | |
2416 } | |
2417 | |
2418 if (is_file($configFile) === true | |
2419 && is_writable($configFile) === false | |
2420 ) { | |
2421 $error = 'Config file '.$configFile.' is not writable'; | |
2422 throw new PHP_CodeSniffer_Exception($error); | |
2423 } | |
2424 }//end if | |
2425 | |
2426 $phpCodeSnifferConfig = self::getAllConfigData(); | |
2427 | |
2428 if ($value === null) { | |
2429 if (isset($phpCodeSnifferConfig[$key]) === true) { | |
2430 unset($phpCodeSnifferConfig[$key]); | |
2431 } | |
2432 } else { | |
2433 $phpCodeSnifferConfig[$key] = $value; | |
2434 } | |
2435 | |
2436 if ($temp === false) { | |
2437 $output = '<'.'?php'."\n".' $phpCodeSnifferConfig = '; | |
2438 $output .= var_export($phpCodeSnifferConfig, true); | |
2439 $output .= "\n?".'>'; | |
2440 | |
2441 if (file_put_contents($configFile, $output) === false) { | |
2442 return false; | |
2443 } | |
2444 } | |
2445 | |
2446 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig; | |
2447 | |
2448 return true; | |
2449 | |
2450 }//end setConfigData() | |
2451 | |
2452 | |
2453 /** | |
2454 * Get all config data in an array. | |
2455 * | |
2456 * @return array<string, string> | |
2457 * @see getConfigData() | |
2458 */ | |
2459 public static function getAllConfigData() | |
2460 { | |
2461 if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']) === true) { | |
2462 return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']; | |
2463 } | |
2464 | |
2465 $path = ''; | |
2466 if (is_callable('Phar::running') === true) { | |
2467 $path = Phar::running(false); | |
2468 } | |
2469 | |
2470 if ($path !== '') { | |
2471 $configFile = dirname($path).'/CodeSniffer.conf'; | |
2472 } else { | |
2473 $configFile = dirname(__FILE__).'/CodeSniffer.conf'; | |
2474 if (is_file($configFile) === false) { | |
2475 $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; | |
2476 } | |
2477 } | |
2478 | |
2479 if (is_file($configFile) === false) { | |
2480 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = array(); | |
2481 return array(); | |
2482 } | |
2483 | |
2484 include $configFile; | |
2485 $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = $phpCodeSnifferConfig; | |
2486 return $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']; | |
2487 | |
2488 }//end getAllConfigData() | |
2489 | |
2490 | |
2491 /** | |
2492 * Return TRUE, if the path is a phar file. | |
2493 * | |
2494 * @param string $path The path to use. | |
2495 * | |
2496 * @return mixed | |
2497 */ | |
2498 public static function isPharFile($path) | |
2499 { | |
2500 if (strpos($path, 'phar://') === 0) { | |
2501 return true; | |
2502 } | |
2503 | |
2504 return false; | |
2505 | |
2506 }//end isPharFile() | |
2507 | |
2508 | |
2509 /** | |
2510 * CodeSniffer alternative for realpath. | |
2511 * | |
2512 * Allows for phar support. | |
2513 * | |
2514 * @param string $path The path to use. | |
2515 * | |
2516 * @return mixed | |
2517 */ | |
2518 public static function realpath($path) | |
2519 { | |
2520 // Support the path replacement of ~ with the user's home directory. | |
2521 if (substr($path, 0, 2) === '~/') { | |
2522 $homeDir = getenv('HOME'); | |
2523 if ($homeDir !== false) { | |
2524 $path = $homeDir.substr($path, 1); | |
2525 } | |
2526 } | |
2527 | |
2528 // No extra work needed if this is not a phar file. | |
2529 if (self::isPharFile($path) === false) { | |
2530 return realpath($path); | |
2531 } | |
2532 | |
2533 // Before trying to break down the file path, | |
2534 // check if it exists first because it will mostly not | |
2535 // change after running the below code. | |
2536 if (file_exists($path) === true) { | |
2537 return $path; | |
2538 } | |
2539 | |
2540 $phar = Phar::running(false); | |
2541 $extra = str_replace('phar://'.$phar, '', $path); | |
2542 $path = realpath($phar); | |
2543 if ($path === false) { | |
2544 return false; | |
2545 } | |
2546 | |
2547 $path = 'phar://'.$path.$extra; | |
2548 if (file_exists($path) === true) { | |
2549 return $path; | |
2550 } | |
2551 | |
2552 return false; | |
2553 | |
2554 }//end realpath() | |
2555 | |
2556 | |
2557 /** | |
2558 * CodeSniffer alternative for chdir(). | |
2559 * | |
2560 * Allows for phar support. | |
2561 * | |
2562 * @param string $path The path to use. | |
2563 * | |
2564 * @return void | |
2565 */ | |
2566 public static function chdir($path) | |
2567 { | |
2568 if (self::isPharFile($path) === true) { | |
2569 $phar = Phar::running(false); | |
2570 chdir(dirname($phar)); | |
2571 } else { | |
2572 chdir($path); | |
2573 } | |
2574 | |
2575 }//end chdir() | |
2576 | |
2577 | |
2578 }//end class |