PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/php/PHP_CodeSniffer/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php

http://github.com/jonswar/perl-code-tidyall
PHP | 281 lines | 181 code | 43 blank | 57 comment | 56 complexity | d3badb57fd163ee0067007ce26fad8b4 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, 0BSD, MIT
  1. <?php
  2. /**
  3. * Ensures a file declares new symbols and causes no other side effects, or executes logic with side effects, but not both.
  4. *
  5. * @author Greg Sherwood <gsherwood@squiz.net>
  6. * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  7. * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  8. */
  9. namespace PHP_CodeSniffer\Standards\PSR1\Sniffs\Files;
  10. use PHP_CodeSniffer\Sniffs\Sniff;
  11. use PHP_CodeSniffer\Files\File;
  12. use PHP_CodeSniffer\Util\Tokens;
  13. class SideEffectsSniff implements Sniff
  14. {
  15. /**
  16. * Returns an array of tokens this test wants to listen for.
  17. *
  18. * @return array
  19. */
  20. public function register()
  21. {
  22. return [T_OPEN_TAG];
  23. }//end register()
  24. /**
  25. * Processes this sniff, when one of its tokens is encountered.
  26. *
  27. * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  28. * @param int $stackPtr The position of the current token in
  29. * the token stack.
  30. *
  31. * @return void
  32. */
  33. public function process(File $phpcsFile, $stackPtr)
  34. {
  35. $tokens = $phpcsFile->getTokens();
  36. $result = $this->searchForConflict($phpcsFile, 0, ($phpcsFile->numTokens - 1), $tokens);
  37. if ($result['symbol'] !== null && $result['effect'] !== null) {
  38. $error = 'A file should declare new symbols (classes, functions, constants, etc.) and cause no other side effects, or it should execute logic with side effects, but should not do both. The first symbol is defined on line %s and the first side effect is on line %s.';
  39. $data = [
  40. $tokens[$result['symbol']]['line'],
  41. $tokens[$result['effect']]['line'],
  42. ];
  43. $phpcsFile->addWarning($error, 0, 'FoundWithSymbols', $data);
  44. $phpcsFile->recordMetric($stackPtr, 'Declarations and side effects mixed', 'yes');
  45. } else {
  46. $phpcsFile->recordMetric($stackPtr, 'Declarations and side effects mixed', 'no');
  47. }
  48. // Ignore the rest of the file.
  49. return ($phpcsFile->numTokens + 1);
  50. }//end process()
  51. /**
  52. * Searches for symbol declarations and side effects.
  53. *
  54. * Returns the positions of both the first symbol declared and the first
  55. * side effect in the file. A NULL value for either indicates nothing was
  56. * found.
  57. *
  58. * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  59. * @param int $start The token to start searching from.
  60. * @param int $end The token to search to.
  61. * @param array $tokens The stack of tokens that make up
  62. * the file.
  63. *
  64. * @return array
  65. */
  66. private function searchForConflict($phpcsFile, $start, $end, $tokens)
  67. {
  68. $symbols = [
  69. T_CLASS => T_CLASS,
  70. T_INTERFACE => T_INTERFACE,
  71. T_TRAIT => T_TRAIT,
  72. T_FUNCTION => T_FUNCTION,
  73. ];
  74. $conditions = [
  75. T_IF => T_IF,
  76. T_ELSE => T_ELSE,
  77. T_ELSEIF => T_ELSEIF,
  78. ];
  79. $checkAnnotations = $phpcsFile->config->annotations;
  80. $firstSymbol = null;
  81. $firstEffect = null;
  82. for ($i = $start; $i <= $end; $i++) {
  83. // Respect phpcs:disable comments.
  84. if ($checkAnnotations === true
  85. && $tokens[$i]['code'] === T_PHPCS_DISABLE
  86. && (empty($tokens[$i]['sniffCodes']) === true
  87. || isset($tokens[$i]['sniffCodes']['PSR1']) === true
  88. || isset($tokens[$i]['sniffCodes']['PSR1.Files']) === true
  89. || isset($tokens[$i]['sniffCodes']['PSR1.Files.SideEffects']) === true)
  90. ) {
  91. do {
  92. $i = $phpcsFile->findNext(T_PHPCS_ENABLE, ($i + 1));
  93. } while ($i !== false
  94. && empty($tokens[$i]['sniffCodes']) === false
  95. && isset($tokens[$i]['sniffCodes']['PSR1']) === false
  96. && isset($tokens[$i]['sniffCodes']['PSR1.Files']) === false
  97. && isset($tokens[$i]['sniffCodes']['PSR1.Files.SideEffects']) === false);
  98. if ($i === false) {
  99. // The entire rest of the file is disabled,
  100. // so return what we have so far.
  101. break;
  102. }
  103. continue;
  104. }
  105. // Ignore whitespace and comments.
  106. if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
  107. continue;
  108. }
  109. // Ignore PHP tags.
  110. if ($tokens[$i]['code'] === T_OPEN_TAG
  111. || $tokens[$i]['code'] === T_CLOSE_TAG
  112. ) {
  113. continue;
  114. }
  115. // Ignore shebang.
  116. if (substr($tokens[$i]['content'], 0, 2) === '#!') {
  117. continue;
  118. }
  119. // Ignore logical operators.
  120. if (isset(Tokens::$booleanOperators[$tokens[$i]['code']]) === true) {
  121. continue;
  122. }
  123. // Ignore entire namespace, declare, const and use statements.
  124. if ($tokens[$i]['code'] === T_NAMESPACE
  125. || $tokens[$i]['code'] === T_USE
  126. || $tokens[$i]['code'] === T_DECLARE
  127. || $tokens[$i]['code'] === T_CONST
  128. ) {
  129. if (isset($tokens[$i]['scope_opener']) === true) {
  130. $i = $tokens[$i]['scope_closer'];
  131. } else {
  132. $semicolon = $phpcsFile->findNext(T_SEMICOLON, ($i + 1));
  133. if ($semicolon !== false) {
  134. $i = $semicolon;
  135. }
  136. }
  137. continue;
  138. }
  139. // Ignore function/class prefixes.
  140. if (isset(Tokens::$methodPrefixes[$tokens[$i]['code']]) === true) {
  141. continue;
  142. }
  143. // Ignore anon classes.
  144. if ($tokens[$i]['code'] === T_ANON_CLASS) {
  145. $i = $tokens[$i]['scope_closer'];
  146. continue;
  147. }
  148. // Detect and skip over symbols.
  149. if (isset($symbols[$tokens[$i]['code']]) === true
  150. && isset($tokens[$i]['scope_closer']) === true
  151. ) {
  152. if ($firstSymbol === null) {
  153. $firstSymbol = $i;
  154. }
  155. $i = $tokens[$i]['scope_closer'];
  156. continue;
  157. } else if ($tokens[$i]['code'] === T_STRING
  158. && strtolower($tokens[$i]['content']) === 'define'
  159. ) {
  160. $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true);
  161. if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR
  162. && $tokens[$prev]['code'] !== T_DOUBLE_COLON
  163. && $tokens[$prev]['code'] !== T_FUNCTION
  164. ) {
  165. if ($firstSymbol === null) {
  166. $firstSymbol = $i;
  167. }
  168. $semicolon = $phpcsFile->findNext(T_SEMICOLON, ($i + 1));
  169. if ($semicolon !== false) {
  170. $i = $semicolon;
  171. }
  172. continue;
  173. }
  174. }//end if
  175. // Special case for defined() as it can be used to see
  176. // if a constant (a symbol) should be defined or not and
  177. // doesn't need to use a full conditional block.
  178. if ($tokens[$i]['code'] === T_STRING
  179. && strtolower($tokens[$i]['content']) === 'defined'
  180. ) {
  181. $openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
  182. if ($tokens[$openBracket]['code'] === T_OPEN_PARENTHESIS
  183. && isset($tokens[$openBracket]['parenthesis_closer']) === true
  184. ) {
  185. $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true);
  186. if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR
  187. && $tokens[$prev]['code'] !== T_DOUBLE_COLON
  188. && $tokens[$prev]['code'] !== T_FUNCTION
  189. ) {
  190. $i = $tokens[$openBracket]['parenthesis_closer'];
  191. continue;
  192. }
  193. }
  194. }//end if
  195. // Conditional statements are allowed in symbol files as long as the
  196. // contents is only a symbol definition. So don't count these as effects
  197. // in this case.
  198. if (isset($conditions[$tokens[$i]['code']]) === true) {
  199. if (isset($tokens[$i]['scope_opener']) === false) {
  200. // Probably an "else if", so just ignore.
  201. continue;
  202. }
  203. $result = $this->searchForConflict(
  204. $phpcsFile,
  205. ($tokens[$i]['scope_opener'] + 1),
  206. ($tokens[$i]['scope_closer'] - 1),
  207. $tokens
  208. );
  209. if ($result['symbol'] !== null) {
  210. if ($firstSymbol === null) {
  211. $firstSymbol = $result['symbol'];
  212. }
  213. if ($result['effect'] !== null) {
  214. // Found a conflict.
  215. $firstEffect = $result['effect'];
  216. break;
  217. }
  218. }
  219. if ($firstEffect === null) {
  220. $firstEffect = $result['effect'];
  221. }
  222. $i = $tokens[$i]['scope_closer'];
  223. continue;
  224. }//end if
  225. if ($firstEffect === null) {
  226. $firstEffect = $i;
  227. }
  228. if ($firstSymbol !== null) {
  229. // We have a conflict we have to report, so no point continuing.
  230. break;
  231. }
  232. }//end for
  233. return [
  234. 'symbol' => $firstSymbol,
  235. 'effect' => $firstEffect,
  236. ];
  237. }//end searchForConflict()
  238. }//end class