PageRenderTime 91ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/web/vendors/PHP/CodeSniffer.php

https://bitbucket.org/bestteam/expenses
PHP | 2137 lines | 1348 code | 272 blank | 517 comment | 231 complexity | e28237372078c1cc3f0cd4bf61432c32 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * PHP_CodeSniffer tokenises 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-2012 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. spl_autoload_register(array('PHP_CodeSniffer', 'autoload'));
  17. if (class_exists('PHP_CodeSniffer_Exception', true) === false) {
  18. throw new Exception('Class PHP_CodeSniffer_Exception not found');
  19. }
  20. if (class_exists('PHP_CodeSniffer_File', true) === false) {
  21. throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_File not found');
  22. }
  23. if (class_exists('PHP_CodeSniffer_Tokens', true) === false) {
  24. throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Tokens not found');
  25. }
  26. if (class_exists('PHP_CodeSniffer_CLI', true) === false) {
  27. throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CLI not found');
  28. }
  29. if (interface_exists('PHP_CodeSniffer_Sniff', true) === false) {
  30. throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_Sniff not found');
  31. }
  32. if (interface_exists('PHP_CodeSniffer_MultiFileSniff', true) === false) {
  33. throw new PHP_CodeSniffer_Exception('Interface PHP_CodeSniffer_MultiFileSniff not found');
  34. }
  35. /**
  36. * PHP_CodeSniffer tokenises PHP code and detects violations of a
  37. * defined set of coding standards.
  38. *
  39. * Standards are specified by classes that implement the PHP_CodeSniffer_Sniff
  40. * interface. A sniff registers what token types it wishes to listen for, then
  41. * PHP_CodeSniffer encounters that token, the sniff is invoked and passed
  42. * information about where the token was found in the stack, and the token stack
  43. * itself.
  44. *
  45. * Sniff files and their containing class must be prefixed with Sniff, and
  46. * have an extension of .php.
  47. *
  48. * Multiple PHP_CodeSniffer operations can be performed by re-calling the
  49. * process function with different parameters.
  50. *
  51. * @category PHP
  52. * @package PHP_CodeSniffer
  53. * @author Greg Sherwood <gsherwood@squiz.net>
  54. * @author Marc McIntyre <mmcintyre@squiz.net>
  55. * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
  56. * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  57. * @version Release: 1.4.3
  58. * @link http://pear.php.net/package/PHP_CodeSniffer
  59. */
  60. class PHP_CodeSniffer
  61. {
  62. /**
  63. * The file or directory that is currently being processed.
  64. *
  65. * @var string
  66. */
  67. protected $file = '';
  68. /**
  69. * The files that have been processed.
  70. *
  71. * @var array(PHP_CodeSniffer_File)
  72. */
  73. protected $files = array();
  74. /**
  75. * A cache of different token types, resolved into arrays.
  76. *
  77. * @var array()
  78. * @see standardiseToken()
  79. */
  80. private static $_resolveTokenCache = array();
  81. /**
  82. * The directory to search for sniffs in.
  83. *
  84. * This is declared static because it is also used in the
  85. * autoloader to look for sniffs outside the PHPCS install.
  86. * This way, standards designed to be installed inside PHPCS can
  87. * also be used from outside the PHPCS Standards directory.
  88. *
  89. * @var string
  90. */
  91. protected static $standardDir = '';
  92. /**
  93. * The CLI object controlling the run.
  94. *
  95. * @var string
  96. */
  97. public $cli = null;
  98. /**
  99. * An array of sniffs that are being used to check files.
  100. *
  101. * @var array(PHP_CodeSniffer_Sniff)
  102. */
  103. protected $listeners = array();
  104. /**
  105. * An array of rules from the ruleset.xml file.
  106. *
  107. * It may be empty, indicating that the ruleset does not override
  108. * any of the default sniff settings.
  109. *
  110. * @var array
  111. */
  112. protected $ruleset = array();
  113. /**
  114. * The listeners array, indexed by token type.
  115. *
  116. * @var array
  117. */
  118. private $_tokenListeners = array(
  119. 'file' => array(),
  120. 'multifile' => array(),
  121. );
  122. /**
  123. * An array of patterns to use for skipping files.
  124. *
  125. * @var array
  126. */
  127. protected $ignorePatterns = array();
  128. /**
  129. * An array of extensions for files we will check.
  130. *
  131. * @var array
  132. */
  133. public $allowedFileExtensions = array(
  134. 'php' => 'PHP',
  135. 'inc' => 'PHP',
  136. 'js' => 'JS',
  137. 'css' => 'CSS',
  138. );
  139. /**
  140. * An array of variable types for param/var we will check.
  141. *
  142. * @var array(string)
  143. */
  144. public static $allowedTypes = array(
  145. 'array',
  146. 'boolean',
  147. 'float',
  148. 'integer',
  149. 'mixed',
  150. 'object',
  151. 'string',
  152. );
  153. /**
  154. * Constructs a PHP_CodeSniffer object.
  155. *
  156. * @param int $verbosity The verbosity level.
  157. * 1: Print progress information.
  158. * 2: Print tokenizer debug information.
  159. * 3: Print sniff debug information.
  160. * @param int $tabWidth The number of spaces each tab represents.
  161. * If greater than zero, tabs will be replaced
  162. * by spaces before testing each file.
  163. * @param string $encoding The charset of the sniffed files.
  164. * This is important for some reports that output
  165. * with utf-8 encoding as you don't want it double
  166. * encoding messages.
  167. * @param bool $interactive If TRUE, will stop after each file with errors
  168. * and wait for user input.
  169. *
  170. * @see process()
  171. */
  172. public function __construct(
  173. $verbosity=0,
  174. $tabWidth=0,
  175. $encoding='iso-8859-1',
  176. $interactive=false
  177. ) {
  178. if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
  179. define('PHP_CODESNIFFER_VERBOSITY', $verbosity);
  180. }
  181. if (defined('PHP_CODESNIFFER_TAB_WIDTH') === false) {
  182. define('PHP_CODESNIFFER_TAB_WIDTH', $tabWidth);
  183. }
  184. if (defined('PHP_CODESNIFFER_ENCODING') === false) {
  185. define('PHP_CODESNIFFER_ENCODING', $encoding);
  186. }
  187. if (defined('PHP_CODESNIFFER_INTERACTIVE') === false) {
  188. define('PHP_CODESNIFFER_INTERACTIVE', $interactive);
  189. }
  190. if (defined('PHPCS_DEFAULT_ERROR_SEV') === false) {
  191. define('PHPCS_DEFAULT_ERROR_SEV', 5);
  192. }
  193. if (defined('PHPCS_DEFAULT_WARN_SEV') === false) {
  194. define('PHPCS_DEFAULT_WARN_SEV', 5);
  195. }
  196. // Change into a directory that we know about to stop any
  197. // relative path conflicts.
  198. if (defined('PHPCS_CWD') === false) {
  199. define('PHPCS_CWD', getcwd());
  200. }
  201. chdir(dirname(__FILE__).'/CodeSniffer/');
  202. // Set default CLI object in case someone is running us
  203. // without using the command line script.
  204. $this->cli = new PHP_CodeSniffer_CLI();
  205. $this->cli->errorSeverity = PHPCS_DEFAULT_ERROR_SEV;
  206. $this->cli->warningSeverity = PHPCS_DEFAULT_WARN_SEV;
  207. }//end __construct()
  208. /**
  209. * Destructs a PHP_CodeSniffer object.
  210. *
  211. * Restores the current working directory to what it
  212. * was before we started our run.
  213. *
  214. * @return void
  215. */
  216. public function __destruct()
  217. {
  218. chdir(PHPCS_CWD);
  219. }//end __destruct()
  220. /**
  221. * Autoload static method for loading classes and interfaces.
  222. *
  223. * @param string $className The name of the class or interface.
  224. *
  225. * @return void
  226. */
  227. public static function autoload($className)
  228. {
  229. if (substr($className, 0, 4) === 'PHP_') {
  230. $newClassName = substr($className, 4);
  231. } else {
  232. $newClassName = $className;
  233. }
  234. $path = str_replace(array('_', '\\'), '/', $newClassName).'.php';
  235. if (is_file(dirname(__FILE__).'/'.$path) === true) {
  236. // Check standard file locations based on class name.
  237. include dirname(__FILE__).'/'.$path;
  238. } else if (is_file(dirname(__FILE__).'/CodeSniffer/Standards/'.$path) === true) {
  239. // Check for included sniffs.
  240. include dirname(__FILE__).'/CodeSniffer/Standards/'.$path;
  241. } else if (self::$standardDir !== ''
  242. && is_file(dirname(self::$standardDir).'/'.$path) === true
  243. ) {
  244. // Check standard file locations based on the passed standard directory.
  245. include dirname(self::$standardDir).'/'.$path;
  246. } else {
  247. // Everything else.
  248. @include $path;
  249. }
  250. }//end autoload()
  251. /**
  252. * Sets an array of file extensions that we will allow checking of.
  253. *
  254. * If the extension is one of the defaults, a specific tokenizer
  255. * will be used. Otherwise, the PHP tokenizer will be used for
  256. * all extensions passed.
  257. *
  258. * @param array $extensions An array of file extensions.
  259. *
  260. * @return void
  261. */
  262. public function setAllowedFileExtensions(array $extensions)
  263. {
  264. $newExtensions = array();
  265. foreach ($extensions as $ext) {
  266. if (isset($this->allowedFileExtensions[$ext]) === true) {
  267. $newExtensions[$ext] = $this->allowedFileExtensions[$ext];
  268. } else {
  269. $newExtensions[$ext] = 'PHP';
  270. }
  271. }
  272. $this->allowedFileExtensions = $newExtensions;
  273. }//end setAllowedFileExtensions()
  274. /**
  275. * Sets an array of ignore patterns that we use to skip files and folders.
  276. *
  277. * Patterns are not case sensitive.
  278. *
  279. * @param array $patterns An array of ignore patterns.
  280. *
  281. * @return void
  282. */
  283. public function setIgnorePatterns(array $patterns)
  284. {
  285. $this->ignorePatterns = $patterns;
  286. }//end setIgnorePatterns()
  287. /**
  288. * Gets the array of ignore patterns.
  289. *
  290. * Optionally takes a listener to get ignore patterns specified
  291. * for that sniff only.
  292. *
  293. * @param string $listener The listener to get patterns for. If NULL, all
  294. * patterns are returned.
  295. *
  296. * @return array
  297. */
  298. public function getIgnorePatterns($listener=null)
  299. {
  300. if ($listener === null) {
  301. return $this->ignorePatterns;
  302. }
  303. if (isset($this->ignorePatterns[$listener]) === true) {
  304. return $this->ignorePatterns[$listener];
  305. }
  306. return array();
  307. }//end getIgnorePatterns()
  308. /**
  309. * Sets the internal CLI object.
  310. *
  311. * @param object $cli The CLI object controlling the run.
  312. *
  313. * @return void
  314. */
  315. public function setCli($cli)
  316. {
  317. $this->cli = $cli;
  318. }//end setCli()
  319. /**
  320. * Adds a file to the list of checked files.
  321. *
  322. * Checked files are used to generate error reports after the run.
  323. *
  324. * @param PHP_CodeSniffer_File $phpcsFile The file to add.
  325. *
  326. * @return void
  327. */
  328. public function addFile(PHP_CodeSniffer_File $phpcsFile)
  329. {
  330. $this->files[] = $phpcsFile;
  331. }//end addFile()
  332. /**
  333. * Processes the files/directories that PHP_CodeSniffer was constructed with.
  334. *
  335. * @param string|array $files The files and directories to process. For
  336. * directories, each sub directory will also
  337. * be traversed for source files.
  338. * @param string $standard The set of code sniffs we are testing
  339. * against.
  340. * @param array $sniffs The sniff names to restrict the allowed
  341. * listeners to.
  342. * @param boolean $local If true, don't recurse into directories.
  343. *
  344. * @return void
  345. * @throws PHP_CodeSniffer_Exception If files or standard are invalid.
  346. */
  347. public function process($files, $standard, array $sniffs=array(), $local=false)
  348. {
  349. if (is_array($files) === false) {
  350. if (is_string($files) === false || $files === null) {
  351. throw new PHP_CodeSniffer_Exception('$file must be a string');
  352. }
  353. $files = array($files);
  354. }
  355. if (is_string($standard) === false || $standard === null) {
  356. throw new PHP_CodeSniffer_Exception('$standard must be a string');
  357. }
  358. // Reset the members.
  359. $this->listeners = array();
  360. $this->files = array();
  361. $this->ruleset = array();
  362. $this->_tokenListeners = array(
  363. 'file' => array(),
  364. 'multifile' => array(),
  365. );
  366. // Ensure this option is enabled or else line endings will not always
  367. // be detected properly for files created on a Mac with the /r line ending.
  368. ini_set('auto_detect_line_endings', true);
  369. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  370. // If this is a custom ruleset.xml file, load the standard name
  371. // from the file. I know this looks a little ugly, but it is
  372. // just when verbose output is on so we have to go to the effort
  373. // of finding the correct name.
  374. $standardName = basename($standard);
  375. if (is_file($standard) === true) {
  376. $ruleset = simplexml_load_file($standard);
  377. if ($ruleset !== false) {
  378. $standardName = (string) $ruleset['name'];
  379. }
  380. } else if (is_file(realpath(PHPCS_CWD.'/'.$standard)) === true) {
  381. $ruleset = simplexml_load_file(realpath(PHPCS_CWD.'/'.$standard));
  382. if ($ruleset !== false) {
  383. $standardName = (string) $ruleset['name'];
  384. }
  385. }
  386. echo "Registering sniffs in $standardName standard... ";
  387. if (PHP_CODESNIFFER_VERBOSITY > 2) {
  388. echo PHP_EOL;
  389. }
  390. }//end if
  391. $this->setTokenListeners($standard, $sniffs);
  392. $this->populateCustomRules();
  393. $this->populateTokenListeners();
  394. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  395. $numSniffs = count($this->listeners);
  396. echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
  397. }
  398. // The SVN pre-commit calls process() to init the sniffs
  399. // and ruleset so there may not be any files to process.
  400. // But this has to come after that initial setup.
  401. if (empty($files) === true) {
  402. return;
  403. }
  404. $reporting = new PHP_CodeSniffer_Reporting();
  405. $cliValues = $this->cli->getCommandLineValues();
  406. $showProgress = $cliValues['showProgress'];
  407. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  408. $numSniffs = count($this->listeners);
  409. echo 'Creating file list... ';
  410. }
  411. $todo = $this->getFilesToProcess($files, $local);
  412. $numFiles = count($todo);
  413. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  414. $numSniffs = count($this->listeners);
  415. echo "DONE ($numFiles files in queue)".PHP_EOL;
  416. }
  417. $numProcessed = 0;
  418. $dots = 0;
  419. $maxLength = strlen($numFiles);
  420. $lastDir = '';
  421. foreach ($todo as $file) {
  422. $this->file = $file;
  423. $currDir = dirname($file);
  424. if ($lastDir !== $currDir) {
  425. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  426. echo 'Changing into directory '.$currDir.PHP_EOL;
  427. }
  428. $lastDir = $currDir;
  429. }
  430. $phpcsFile = $this->processFile($file);
  431. $numProcessed++;
  432. if (PHP_CODESNIFFER_VERBOSITY > 0
  433. || PHP_CODESNIFFER_INTERACTIVE === true
  434. || $showProgress === false
  435. ) {
  436. continue;
  437. }
  438. // Show progress information.
  439. if ($phpcsFile === null) {
  440. echo 'S';
  441. } else {
  442. $errors = $phpcsFile->getErrorCount();
  443. $warnings = $phpcsFile->getWarningCount();
  444. if ($errors > 0) {
  445. echo 'E';
  446. } else if ($warnings > 0) {
  447. echo 'W';
  448. } else {
  449. echo '.';
  450. }
  451. }
  452. $dots++;
  453. if ($dots === 60) {
  454. $padding = ($maxLength - strlen($numProcessed));
  455. echo str_repeat(' ', $padding);
  456. echo " $numProcessed / $numFiles".PHP_EOL;
  457. $dots = 0;
  458. }
  459. }//end foreach
  460. if (PHP_CODESNIFFER_VERBOSITY === 0
  461. && PHP_CODESNIFFER_INTERACTIVE === false
  462. && $showProgress === true
  463. ) {
  464. echo PHP_EOL.PHP_EOL;
  465. }
  466. // Now process the multi-file sniffs, assuming there are
  467. // multiple files being sniffed.
  468. if (count($files) > 1 || is_dir($files[0]) === true) {
  469. $this->processMulti();
  470. }
  471. }//end process()
  472. /**
  473. * Processes multi-file sniffs.
  474. *
  475. * @return void
  476. */
  477. public function processMulti()
  478. {
  479. foreach ($this->_tokenListeners['multifile'] as $listenerData) {
  480. // Set the name of the listener for error messages.
  481. foreach ($this->files as $file) {
  482. $file->setActiveListener($listenerData['class']);
  483. }
  484. $listenerData['listener']->process($this->files);
  485. }
  486. }//end processMulti()
  487. /**
  488. * Sets installed sniffs in the coding standard being used.
  489. *
  490. * Traverses the standard directory for classes that implement the
  491. * PHP_CodeSniffer_Sniff interface asks them to register. Each of the
  492. * sniff's class names must be exact as the basename of the sniff file.
  493. * If the standard is a file, will skip transversal and just load sniffs
  494. * from the file.
  495. *
  496. * @param string $standard The name of the coding standard we are checking.
  497. * Can also be a path to a custom standard dir
  498. * containing a ruleset.xml file or can be a path
  499. * to a custom ruleset file.
  500. * @param array $sniffs The sniff names to restrict the allowed
  501. * listeners to.
  502. *
  503. * @return void
  504. * @throws PHP_CodeSniffer_Exception If the standard is not valid.
  505. */
  506. public function setTokenListeners($standard, array $sniffs=array())
  507. {
  508. if (is_dir($standard) === true) {
  509. // This is an absolute path to a custom standard.
  510. self::$standardDir = $standard;
  511. $standard = basename($standard);
  512. } else if (is_file($standard) === true) {
  513. // Might be a custom ruleset file.
  514. $ruleset = simplexml_load_file($standard);
  515. if ($ruleset === false) {
  516. throw new PHP_CodeSniffer_Exception("Ruleset $standard is not valid");
  517. }
  518. if (basename($standard) === 'ruleset.xml') {
  519. // The ruleset uses the generic name, so this may actually
  520. // be a complete standard with it's own sniffs. By setting the
  521. // the standardDir to be the directory, we will process both
  522. // the directory (for custom sniffs) and the ruleset.xml file
  523. // (as it uses the generic name) in getSniffFiles.
  524. self::$standardDir = dirname($standard);
  525. } else {
  526. // This is a custom ruleset file with a custom name, so we have
  527. // to assume there are no custom sniffs to go with this otherwise
  528. // we'd be recursing through directories on every run, even if
  529. // we don't need to.
  530. self::$standardDir = $standard;
  531. }
  532. $standard = (string) $ruleset['name'];
  533. } else {
  534. self::$standardDir = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$standard);
  535. if (is_dir(self::$standardDir) === false) {
  536. // This isn't looking good. Let's see if this
  537. // is a relative path to a custom standard.
  538. $path = realpath(PHPCS_CWD.'/'.$standard);
  539. if (is_dir($path) === true) {
  540. // This is a relative path to a custom standard.
  541. self::$standardDir = $path;
  542. $standard = basename($standard);
  543. } else if (is_file($path) === true) {
  544. // Might be a custom ruleset file.
  545. $ruleset = simplexml_load_file($path);
  546. if ($ruleset === false) {
  547. throw new PHP_CodeSniffer_Exception("Ruleset $path is not valid");
  548. }
  549. // See comments in ELSE IF condition above for why we do this.
  550. if (basename($path) === 'ruleset.xml') {
  551. self::$standardDir = dirname($path);
  552. } else {
  553. self::$standardDir = $path;
  554. }
  555. $standard = (string) $ruleset['name'];
  556. }
  557. }
  558. }//end if
  559. $files = $this->getSniffFiles(self::$standardDir, $standard);
  560. if (empty($sniffs) === false) {
  561. // Convert the allowed sniffs to lower case so
  562. // they are easier to check.
  563. foreach ($sniffs as &$sniff) {
  564. $sniff = strtolower($sniff);
  565. }
  566. }
  567. $listeners = array();
  568. foreach ($files as $file) {
  569. // Work out where the position of /StandardName/Sniffs/... is
  570. // so we can determine what the class will be called.
  571. $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
  572. if ($sniffPos === false) {
  573. continue;
  574. }
  575. $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR);
  576. if ($slashPos === false) {
  577. continue;
  578. }
  579. $className = substr($file, ($slashPos + 1));
  580. $className = substr($className, 0, -4);
  581. $className = str_replace(DIRECTORY_SEPARATOR, '_', $className);
  582. include_once $file;
  583. // Support the use of PHP namespaces. If the class name we included
  584. // contains namespace seperators instead of underscores, use this as the
  585. // class name from now on.
  586. $classNameNS = str_replace('_', '\\', $className);
  587. if (class_exists($classNameNS, false) === true) {
  588. $className = $classNameNS;
  589. }
  590. // If they have specified a list of sniffs to restrict to, check
  591. // to see if this sniff is allowed.
  592. $allowed = in_array(strtolower($className), $sniffs);
  593. if (empty($sniffs) === false && $allowed === false) {
  594. continue;
  595. }
  596. $listeners[$className] = $className;
  597. if (PHP_CODESNIFFER_VERBOSITY > 2) {
  598. echo "\tRegistered $className".PHP_EOL;
  599. }
  600. }//end foreach
  601. $this->listeners = $listeners;
  602. }//end setTokenListeners()
  603. /**
  604. * Return a list of sniffs that a coding standard has defined.
  605. *
  606. * Sniffs are found by recursing the standard directory and also by
  607. * asking the standard for included sniffs.
  608. *
  609. * @param string $dir The directory where to look for the files.
  610. * @param string $standard The name of the coding standard. If NULL, no
  611. * included sniffs will be checked for.
  612. *
  613. * @return array
  614. * @throws PHP_CodeSniffer_Exception If an included or excluded sniff does
  615. * not exist.
  616. */
  617. public function getSniffFiles($dir, $standard=null)
  618. {
  619. $ownSniffs = array();
  620. $includedSniffs = array();
  621. $excludedSniffs = array();
  622. if (is_dir($dir) === true) {
  623. $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
  624. foreach ($di as $file) {
  625. $fileName = $file->getFilename();
  626. // Skip hidden files.
  627. if (substr($fileName, 0, 1) === '.') {
  628. continue;
  629. }
  630. // We are only interested in PHP and sniff files.
  631. $fileParts = explode('.', $fileName);
  632. if (array_pop($fileParts) !== 'php') {
  633. continue;
  634. }
  635. $basename = basename($fileName, '.php');
  636. if (substr($basename, -5) !== 'Sniff') {
  637. continue;
  638. }
  639. $ownSniffs[] = $file->getPathname();
  640. }//end foreach
  641. }//end if
  642. if ($standard !== null) {
  643. $rulesetPath = $dir;
  644. if (is_dir($rulesetPath) === true) {
  645. $rulesetPath .= '/ruleset.xml';
  646. }
  647. $ruleset = simplexml_load_file($rulesetPath);
  648. if ($ruleset === false) {
  649. throw new PHP_CodeSniffer_Exception("Ruleset $rulesetPath is not valid");
  650. }
  651. foreach ($ruleset->rule as $rule) {
  652. $includedSniffs = array_merge($includedSniffs, $this->_expandRulesetReference($rule['ref']));
  653. if (isset($rule->exclude) === true) {
  654. foreach ($rule->exclude as $exclude) {
  655. $excludedSniffs = array_merge($excludedSniffs, $this->_expandRulesetReference($exclude['name']));
  656. }
  657. }
  658. }//end foreach
  659. }//end if
  660. $includedSniffs = array_unique($includedSniffs);
  661. $excludedSniffs = array_unique($excludedSniffs);
  662. // Merge our own sniff list with our externally included
  663. // sniff list, but filter out any excluded sniffs.
  664. $files = array();
  665. foreach (array_merge($ownSniffs, $includedSniffs) as $sniff) {
  666. if (in_array($sniff, $excludedSniffs) === true) {
  667. continue;
  668. } else {
  669. $files[] = realpath($sniff);
  670. }
  671. }
  672. return array_unique($files);
  673. }//end getSniffFiles()
  674. /**
  675. * Expand a ruleset sniff reference into a list of sniff files.
  676. *
  677. * @param string $sniff The sniff reference from the rulset.xml file.
  678. *
  679. * @return array
  680. * @throws PHP_CodeSniffer_Exception If the sniff reference is invalid.
  681. */
  682. private function _expandRulesetReference($sniff)
  683. {
  684. $referencedSniffs = array();
  685. // Ignore internal sniffs as they are used to only
  686. // hide and change internal messages.
  687. if (substr($sniff, 0, 9) === 'Internal.') {
  688. return $referencedSniffs;
  689. }
  690. // As sniffs can't begin with a full stop, assume sniffs in
  691. // this format are relative paths and attempt to convert them
  692. // to absolute paths. If this fails, let the sniff path run through
  693. // the normal checks and have it fail as normal.
  694. if (substr($sniff, 0, 1) === '.') {
  695. $standardDir = self::$standardDir;
  696. if (substr(self::$standardDir, -4) === '.xml') {
  697. $standardDir = dirname($standardDir);
  698. }
  699. $realpath = realpath($standardDir.'/'.$sniff);
  700. if ($realpath !== false) {
  701. $sniff = $realpath;
  702. }
  703. }
  704. $isDir = false;
  705. $path = $sniff;
  706. if (is_dir($sniff) === true) {
  707. // Referencing a custom standard.
  708. $isDir = true;
  709. $path = $sniff;
  710. $sniff = basename($path);
  711. } else if (is_file($sniff) === false) {
  712. // See if this is a whole standard being referenced.
  713. $path = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$sniff);
  714. if (is_dir($path) === true) {
  715. $isDir = true;
  716. } else {
  717. // Work out the sniff path.
  718. $parts = explode('.', $sniff);
  719. if (count($parts) < 3) {
  720. $error = "Referenced sniff $sniff does not exist";
  721. throw new PHP_CodeSniffer_Exception($error);
  722. }
  723. $path = $parts[0].'/Sniffs/'.$parts[1].'/'.$parts[2].'Sniff.php';
  724. $path = realpath(dirname(__FILE__).'/CodeSniffer/Standards/'.$path);
  725. if ($path === false && self::$standardDir !== '') {
  726. // The sniff is not locally installed, so check if it is being
  727. // referenced as a remote sniff outside the install. We do this by
  728. // looking directly in the passed standard dir to see if it is
  729. // installed in there.
  730. $path = realpath(self::$standardDir.'/Sniffs/'.$parts[1].'/'.$parts[2].'Sniff.php');
  731. }
  732. }
  733. }//end if
  734. if ($isDir === true) {
  735. if (self::isInstalledStandard($sniff) === true) {
  736. // We are referencing a coding standard.
  737. $referencedSniffs = $this->getSniffFiles($path, $sniff);
  738. $this->populateCustomRules($path);
  739. } else {
  740. // We are referencing a whole directory of sniffs.
  741. $referencedSniffs = $this->getSniffFiles($path);
  742. }
  743. } else {
  744. if (is_file($path) === false) {
  745. $error = "Referenced sniff $sniff does not exist";
  746. throw new PHP_CodeSniffer_Exception($error);
  747. }
  748. if (substr($path, -9) === 'Sniff.php') {
  749. // A single sniff.
  750. $referencedSniffs[] = $path;
  751. } else {
  752. // Assume an external ruleset.xml file.
  753. $referencedSniffs = $this->getSniffFiles($path, $sniff);
  754. }
  755. }//end if
  756. return $referencedSniffs;
  757. }//end _expandRulesetReference()
  758. /**
  759. * Sets installed sniffs in the coding standard being used.
  760. *
  761. * @param string $standard The name of the coding standard we are checking.
  762. * Can also be a path to a custom ruleset.xml file.
  763. *
  764. * @return void
  765. */
  766. public function populateCustomRules($standard=null)
  767. {
  768. if ($standard === null) {
  769. $standard = self::$standardDir;
  770. }
  771. if (is_file($standard) === false) {
  772. $standard .= '/ruleset.xml';
  773. if (is_file($standard) === false) {
  774. return;
  775. }
  776. }
  777. $ruleset = simplexml_load_file($standard);
  778. foreach ($ruleset->rule as $rule) {
  779. if (isset($rule['ref']) === false) {
  780. continue;
  781. }
  782. $code = (string) $rule['ref'];
  783. // Custom severity.
  784. if (isset($rule->severity) === true) {
  785. if (isset($this->ruleset[$code]) === false) {
  786. $this->ruleset[$code] = array();
  787. }
  788. $this->ruleset[$code]['severity'] = (int) $rule->severity;
  789. }
  790. // Custom message type.
  791. if (isset($rule->type) === true) {
  792. if (isset($this->ruleset[$code]) === false) {
  793. $this->ruleset[$code] = array();
  794. }
  795. $this->ruleset[$code]['type'] = (string) $rule->type;
  796. }
  797. // Custom message.
  798. if (isset($rule->message) === true) {
  799. if (isset($this->ruleset[$code]) === false) {
  800. $this->ruleset[$code] = array();
  801. }
  802. $this->ruleset[$code]['message'] = (string) $rule->message;
  803. }
  804. // Custom properties.
  805. if (isset($rule->properties) === true) {
  806. foreach ($rule->properties->property as $prop) {
  807. if (isset($this->ruleset[$code]) === false) {
  808. $this->ruleset[$code] = array(
  809. 'properties' => array(),
  810. );
  811. } else if (isset($this->ruleset[$code]['properties']) === false) {
  812. $this->ruleset[$code]['properties'] = array();
  813. }
  814. $name = (string) $prop['name'];
  815. if (isset($prop['type']) === true
  816. && (string) $prop['type'] === 'array'
  817. ) {
  818. $value = (string) $prop['value'];
  819. $this->ruleset[$code]['properties'][$name] = explode(',', $value);
  820. } else {
  821. $this->ruleset[$code]['properties'][$name] = (string) $prop['value'];
  822. }
  823. }
  824. }//end if
  825. // Ignore patterns.
  826. foreach ($rule->{'exclude-pattern'} as $pattern) {
  827. if (isset($this->ignorePatterns[$code]) === false) {
  828. $this->ignorePatterns[$code] = array();
  829. }
  830. if (isset($pattern['type']) === false) {
  831. $pattern['type'] = 'absolute';
  832. }
  833. $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type'];
  834. }
  835. }//end foreach
  836. // Process custom ignore pattern rules.
  837. foreach ($ruleset->{'exclude-pattern'} as $pattern) {
  838. if (isset($pattern['type']) === false) {
  839. $pattern['type'] = 'absolute';
  840. }
  841. $this->ignorePatterns[(string) $pattern] = (string) $pattern['type'];
  842. }
  843. }//end populateCustomRules()
  844. /**
  845. * Populates the array of PHP_CodeSniffer_Sniff's for this file.
  846. *
  847. * @return void
  848. * @throws PHP_CodeSniffer_Exception If sniff registration fails.
  849. */
  850. public function populateTokenListeners()
  851. {
  852. // Construct a list of listeners indexed by token being listened for.
  853. $this->_tokenListeners = array(
  854. 'file' => array(),
  855. 'multifile' => array(),
  856. );
  857. foreach ($this->listeners as $listenerClass) {
  858. // Work out the internal code for this sniff. Detect usage of namespace
  859. // seperators instead of underscores to support PHP namespaces.
  860. if (strstr($listenerClass, '\\') === false) {
  861. $parts = explode('_', $listenerClass);
  862. } else {
  863. $parts = explode('\\', $listenerClass);
  864. }
  865. $code = $parts[0].'.'.$parts[2].'.'.$parts[3];
  866. $code = substr($code, 0, -5);
  867. $this->listeners[$listenerClass] = new $listenerClass();
  868. // Set custom properties.
  869. if (isset($this->ruleset[$code]['properties']) === true) {
  870. foreach ($this->ruleset[$code]['properties'] as $name => $value) {
  871. $this->setSniffProperty($listenerClass, $name, $value);
  872. }
  873. }
  874. $tokenizers = array('PHP');
  875. $vars = get_class_vars($listenerClass);
  876. if (isset($vars['supportedTokenizers']) === true) {
  877. $tokenizers = $vars['supportedTokenizers'];
  878. }
  879. if (($this->listeners[$listenerClass] instanceof PHP_CodeSniffer_Sniff) === true) {
  880. $tokens = $this->listeners[$listenerClass]->register();
  881. if (is_array($tokens) === false) {
  882. $msg = "Sniff $listenerClass register() method must return an array";
  883. throw new PHP_CodeSniffer_Exception($msg);
  884. }
  885. foreach ($tokens as $token) {
  886. if (isset($this->_tokenListeners['file'][$token]) === false) {
  887. $this->_tokenListeners['file'][$token] = array();
  888. }
  889. if (in_array($this->listeners[$listenerClass], $this->_tokenListeners['file'][$token], true) === false) {
  890. $this->_tokenListeners['file'][$token][] = array(
  891. 'listener' => $this->listeners[$listenerClass],
  892. 'class' => $listenerClass,
  893. 'tokenizers' => $tokenizers,
  894. );
  895. }
  896. }
  897. } else if (($this->listeners[$listenerClass] instanceof PHP_CodeSniffer_MultiFileSniff) === true) {
  898. $this->_tokenListeners['multifile'][] = array(
  899. 'listener' => $this->listeners[$listenerClass],
  900. 'class' => $listenerClass,
  901. 'tokenizers' => $tokenizers,
  902. );
  903. }
  904. }//end foreach
  905. }//end populateTokenListeners()
  906. /**
  907. * Set a single property for a sniff.
  908. *
  909. * @param string $listenerClass The class name of the sniff.
  910. * @param string $name The name of the property to change.
  911. * @param string $value The new value of the property.
  912. *
  913. * @return void
  914. */
  915. public function setSniffProperty($listenerClass, $name, $value)
  916. {
  917. // Setting a property for a sniff we are not using.
  918. if (isset($this->listeners[$listenerClass]) === false) {
  919. return;
  920. }
  921. $name = trim($name);
  922. if (is_string($value) === true) {
  923. $value = trim($value);
  924. }
  925. // Special case for booleans.
  926. if ($value === 'true') {
  927. $value = true;
  928. } else if ($value === 'false') {
  929. $value = false;
  930. }
  931. $this->listeners[$listenerClass]->$name = $value;
  932. }//end setSniffProperty()
  933. /**
  934. * Get a list of files that will be processed.
  935. *
  936. * If passed directories, this method will find all files within them.
  937. * The method will also perform file extension and ignore pattern filtering.
  938. *
  939. * @param string $paths A list of file or directory paths to process.
  940. * @param boolean $local If true, only process 1 level of files in directories
  941. *
  942. * @return array
  943. * @throws Exception If there was an error opening a directory.
  944. * @see shouldProcessFile()
  945. */
  946. public function getFilesToProcess($paths, $local=false)
  947. {
  948. $files = array();
  949. foreach ($paths as $path) {
  950. if (is_dir($path) === true) {
  951. if ($local === true) {
  952. $di = new DirectoryIterator($path);
  953. } else {
  954. $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
  955. }
  956. foreach ($di as $file) {
  957. // Check if the file exists after all symlinks are reolved.
  958. $filePath = realpath($file->getPathname());
  959. if ($filePath === false) {
  960. continue;
  961. }
  962. if (is_dir($filePath) === true) {
  963. continue;
  964. }
  965. if ($this->shouldProcessFile($file->getPathname(), $path) === false) {
  966. continue;
  967. }
  968. $files[] = $file->getPathname();
  969. }//end foreach
  970. } else {
  971. if ($this->shouldIgnoreFile($path, dirname($path)) === true) {
  972. continue;
  973. }
  974. $files[] = $path;
  975. }//end if
  976. }//end foreach
  977. return $files;
  978. }//end getFilesToProcess()
  979. /**
  980. * Checks filtering rules to see if a file should be checked.
  981. *
  982. * Checks both file extension filters and path ignore filters.
  983. *
  984. * @param string $path The path to the file being checked.
  985. * @param string $basedir The directory to use for relative path checks.
  986. *
  987. * @return bool
  988. */
  989. public function shouldProcessFile($path, $basedir)
  990. {
  991. // Check that the file's extension is one we are checking.
  992. // We are strict about checking the extension and we don't
  993. // let files through with no extension or that start with a dot.
  994. $fileName = basename($path);
  995. $fileParts = explode('.', $fileName);
  996. if ($fileParts[0] === $fileName || $fileParts[0] === '') {
  997. return false;
  998. }
  999. // Checking multi-part file extensions, so need to create a
  1000. // complete extension list and make sure one is allowed.
  1001. $extensions = array();
  1002. array_shift($fileParts);
  1003. foreach ($fileParts as $part) {
  1004. $extensions[implode('.', $fileParts)] = 1;
  1005. array_shift($fileParts);
  1006. }
  1007. $matches = array_intersect_key($extensions, $this->allowedFileExtensions);
  1008. if (empty($matches) === true) {
  1009. return false;
  1010. }
  1011. // If the file's path matches one of our ignore patterns, skip it.
  1012. if ($this->shouldIgnoreFile($path, $basedir) === true) {
  1013. return false;
  1014. }
  1015. return true;
  1016. }//end shouldProcessFile()
  1017. /**
  1018. * Checks filtering rules to see if a file should be ignored.
  1019. *
  1020. * @param string $path The path to the file being checked.
  1021. * @param string $basedir The directory to use for relative path checks.
  1022. *
  1023. * @return bool
  1024. */
  1025. public function shouldIgnoreFile($path, $basedir)
  1026. {
  1027. $relativePath = $path;
  1028. if (strpos($path, $basedir) === 0) {
  1029. // The +1 cuts off the directory separator as well.
  1030. $relativePath = substr($path, (strlen($basedir) + 1));
  1031. }
  1032. foreach ($this->ignorePatterns as $pattern => $type) {
  1033. if (is_array($pattern) === true) {
  1034. // A sniff specific ignore pattern.
  1035. continue;
  1036. }
  1037. $replacements = array(
  1038. '\\,' => ',',
  1039. '*' => '.*',
  1040. );
  1041. // We assume a / directory seperator, as do the exclude rules
  1042. // most developers write, so we need a special case for any system
  1043. // that is different.
  1044. if (DIRECTORY_SEPARATOR === '\\') {
  1045. $replacements['/'] = '\\\\';
  1046. }
  1047. $pattern = strtr($pattern, $replacements);
  1048. if ($type === 'relative') {
  1049. $testPath = $relativePath;
  1050. } else {
  1051. $testPath = $path;
  1052. }
  1053. if (preg_match("|{$pattern}|i", $testPath) === 1) {
  1054. return true;
  1055. }
  1056. }//end foreach
  1057. return false;
  1058. }//end shouldIgnoreFile()
  1059. /**
  1060. * Run the code sniffs over a single given file.
  1061. *
  1062. * Processes the file and runs the PHP_CodeSniffer sniffs to verify that it
  1063. * conforms with the standard. Returns the processed file object, or NULL
  1064. * if no file was processed due to error.
  1065. *
  1066. * @param string $file The file to process.
  1067. * @param string $contents The contents to parse. If NULL, the content
  1068. * is taken from the file system.
  1069. *
  1070. * @return PHP_CodeSniffer_File
  1071. * @throws PHP_CodeSniffer_Exception If the file could not be processed.
  1072. * @see _processFile()
  1073. */
  1074. public function processFile($file, $contents=null)
  1075. {
  1076. if ($contents === null && file_exists($file) === false) {
  1077. throw new PHP_CodeSniffer_Exception("Source file $file does not exist");
  1078. }
  1079. $filePath = realpath($file);
  1080. if ($filePath === false) {
  1081. $filePath = $file;
  1082. }
  1083. // Before we go and spend time tokenizing this file, just check
  1084. // to see if there is a tag up top to indicate that the whole
  1085. // file should be ignored. It must be on one of the first two lines.
  1086. $firstContent = $contents;
  1087. if ($contents === null && is_readable($filePath) === true) {
  1088. $handle = fopen($filePath, 'r');
  1089. if ($handle !== false) {
  1090. $firstContent = fgets($handle);
  1091. $firstContent .= fgets($handle);
  1092. fclose($handle);
  1093. }
  1094. }
  1095. if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false) {
  1096. // We are ignoring the whole file.
  1097. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  1098. echo 'Ignoring '.basename($filePath).PHP_EOL;
  1099. }
  1100. return null;
  1101. }
  1102. try {
  1103. $phpcsFile = $this->_processFile($file, $contents);
  1104. } catch (Exception $e) {
  1105. $trace = $e->getTrace();
  1106. $filename = $trace[0]['args'][0];
  1107. if (is_object($filename) === true
  1108. && get_class($filename) === 'PHP_CodeSniffer_File'
  1109. ) {
  1110. $filename = $filename->getFilename();
  1111. } else if (is_numeric($filename) === true) {
  1112. // See if we can find the PHP_CodeSniffer_File object.
  1113. foreach ($trace as $data) {
  1114. if (isset($data['args'][0]) === true
  1115. && ($data['args'][0] instanceof PHP_CodeSniffer_File) === true
  1116. ) {
  1117. $filename = $data['args'][0]->getFilename();
  1118. }
  1119. }
  1120. } else if (is_string($filename) === false) {
  1121. $filename = (string) $filename;
  1122. }
  1123. $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
  1124. $phpcsFile = new PHP_CodeSniffer_File(
  1125. $filename,
  1126. $this->_tokenListeners['file'],
  1127. $this->allowedFileExtensions,
  1128. $this->ruleset,
  1129. $this
  1130. );
  1131. $this->addFile($phpcsFile);
  1132. $phpcsFile->addError($error, null);
  1133. }//end try
  1134. if (PHP_CODESNIFFER_INTERACTIVE === false) {
  1135. return $phpcsFile;
  1136. }
  1137. /*
  1138. Running interactively.
  1139. Print the error report for the current file and then wait for user input.
  1140. */
  1141. $reporting = new PHP_CodeSniffer_Reporting();
  1142. $cliValues = $this->cli->getCommandLineValues();
  1143. // Get current violations and then clear the list to make sure
  1144. // we only print violations for a single file each time.
  1145. $numErrors = null;
  1146. while ($numErrors !== 0) {
  1147. $filesViolations = $this->getFilesErrors();
  1148. $this->files = array();
  1149. $numErrors = $reporting->printReport(
  1150. 'full',
  1151. $filesViolations,
  1152. $cliValues['showSources'],
  1153. null,
  1154. $cliValues['reportWidth']
  1155. );
  1156. if ($numErrors === 0) {
  1157. continue;
  1158. }
  1159. echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
  1160. $input = fgets(STDIN);
  1161. $input = trim($input);
  1162. switch ($input) {
  1163. case 's':
  1164. break;
  1165. case 'q':
  1166. exit(0);
  1167. break;
  1168. default:
  1169. // Repopulate the sniffs because some of them save their state
  1170. // and only clear it when the file changes, but we are rechecking
  1171. // the same file.
  1172. $this->populateTokenListeners();
  1173. $phpcsFile = $this->_processFile($file, $contents);
  1174. break;
  1175. }
  1176. }//end while
  1177. return $phpcsFile;
  1178. }//end processFile()
  1179. /**
  1180. * Process the sniffs for a single file.
  1181. *
  1182. * Does raw processing only. No interactive support or error checking.
  1183. *
  1184. * @param string $file The file to process.
  1185. * @param string $contents The contents to parse. If NULL, the content
  1186. * is taken from the file system.
  1187. *
  1188. * @return PHP_CodeSniffer_File
  1189. * @see processFile()
  1190. */
  1191. private function _processFile($file, $contents)
  1192. {
  1193. if (PHP_CODESNIFFER_VERBOSITY > 0) {
  1194. $startTime = time();
  1195. echo 'Processing '.basename($file).' ';
  1196. if (PHP_CODESNIFFER_VERBOSITY > 1) {
  1197. echo PHP_EOL;
  1198. }
  1199. }
  1200. $phpcsFile = new PHP_CodeSniffer_File(
  1201. $file,
  1202. $this->_tokenListeners['file'],
  1203. $this->allowedFileExtensions,
  1204. $this->ruleset,
  1205. $this
  1206. );
  1207. $this->addFile($phpcsFile);
  1208. $phpcsFile->start($contents);
  1209. // Clean up the test if we can to save memory. This can't be done if
  1210. // we need to leave the files around for multi-file sniffs.
  1211. if (PHP_CODESNIFFER_INTERACTIVE === false
  1212. && empty($this->_tokenListeners['multifile']) === true
  1213. ) {
  1214. $phpcsFile->cleanUp()

Large files files are truncated, but you can click here to view the full file