PageRenderTime 63ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/php/PHP_CodeSniffer/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php

http://github.com/jonswar/perl-code-tidyall
PHP | 233 lines | 154 code | 34 blank | 45 comment | 29 complexity | 313377363b681107ec24d241bf2fb843 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, 0BSD, MIT
  1. <?php
  2. /**
  3. * Verifies that a @throws tag exists for each exception type a function throws.
  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\Squiz\Sniffs\Commenting;
  10. use PHP_CodeSniffer\Sniffs\Sniff;
  11. use PHP_CodeSniffer\Files\File;
  12. use PHP_CodeSniffer\Util\Tokens;
  13. class FunctionCommentThrowTagSniff 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_FUNCTION];
  23. }//end register()
  24. /**
  25. * Processes this test, 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
  29. * in the stack passed in $tokens.
  30. *
  31. * @return void
  32. */
  33. public function process(File $phpcsFile, $stackPtr)
  34. {
  35. $tokens = $phpcsFile->getTokens();
  36. if (isset($tokens[$stackPtr]['scope_closer']) === false) {
  37. // Abstract or incomplete.
  38. return;
  39. }
  40. $find = Tokens::$methodPrefixes;
  41. $find[] = T_WHITESPACE;
  42. $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
  43. if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
  44. // Function doesn't have a doc comment or is using the wrong type of comment.
  45. return;
  46. }
  47. $stackPtrEnd = $tokens[$stackPtr]['scope_closer'];
  48. // Find all the exception type tokens within the current scope.
  49. $thrownExceptions = [];
  50. $currPos = $stackPtr;
  51. $foundThrows = false;
  52. $unknownCount = 0;
  53. do {
  54. $currPos = $phpcsFile->findNext([T_THROW, T_ANON_CLASS, T_CLOSURE], ($currPos + 1), $stackPtrEnd);
  55. if ($currPos === false) {
  56. break;
  57. }
  58. if ($tokens[$currPos]['code'] !== T_THROW) {
  59. $currPos = $tokens[$currPos]['scope_closer'];
  60. continue;
  61. }
  62. $foundThrows = true;
  63. /*
  64. If we can't find a NEW, we are probably throwing
  65. a variable or calling a method.
  66. If we're throwing a variable, and it's the same variable as the
  67. exception container from the nearest 'catch' block, we take that exception
  68. as it is likely to be a re-throw.
  69. If we can't find a matching catch block, or the variable name
  70. is different, it's probably a different variable, so we ignore it,
  71. but they still need to provide at least one @throws tag, even through we
  72. don't know the exception class.
  73. */
  74. $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($currPos + 1), null, true);
  75. if ($tokens[$nextToken]['code'] === T_NEW
  76. || $tokens[$nextToken]['code'] === T_NS_SEPARATOR
  77. || $tokens[$nextToken]['code'] === T_STRING
  78. ) {
  79. if ($tokens[$nextToken]['code'] === T_NEW) {
  80. $currException = $phpcsFile->findNext(
  81. [
  82. T_NS_SEPARATOR,
  83. T_STRING,
  84. ],
  85. $currPos,
  86. $stackPtrEnd,
  87. false,
  88. null,
  89. true
  90. );
  91. } else {
  92. $currException = $nextToken;
  93. }
  94. if ($currException !== false) {
  95. $endException = $phpcsFile->findNext(
  96. [
  97. T_NS_SEPARATOR,
  98. T_STRING,
  99. ],
  100. ($currException + 1),
  101. $stackPtrEnd,
  102. true,
  103. null,
  104. true
  105. );
  106. if ($endException === false) {
  107. $thrownExceptions[] = $tokens[$currException]['content'];
  108. } else {
  109. $thrownExceptions[] = $phpcsFile->getTokensAsString($currException, ($endException - $currException));
  110. }
  111. }//end if
  112. } else if ($tokens[$nextToken]['code'] === T_VARIABLE) {
  113. // Find the nearest catch block in this scope and, if the caught var
  114. // matches our rethrown var, use the exception types being caught as
  115. // exception types that are being thrown as well.
  116. $catch = $phpcsFile->findPrevious(
  117. T_CATCH,
  118. $currPos,
  119. $tokens[$stackPtr]['scope_opener'],
  120. false,
  121. null,
  122. false
  123. );
  124. if ($catch !== false) {
  125. $thrownVar = $phpcsFile->findPrevious(
  126. T_VARIABLE,
  127. ($tokens[$catch]['parenthesis_closer'] - 1),
  128. $tokens[$catch]['parenthesis_opener']
  129. );
  130. if ($tokens[$thrownVar]['content'] === $tokens[$nextToken]['content']) {
  131. $exceptions = explode('|', $phpcsFile->getTokensAsString(($tokens[$catch]['parenthesis_opener'] + 1), ($thrownVar - $tokens[$catch]['parenthesis_opener'] - 1)));
  132. foreach ($exceptions as $exception) {
  133. $thrownExceptions[] = trim($exception);
  134. }
  135. }
  136. }
  137. } else {
  138. ++$unknownCount;
  139. }//end if
  140. } while ($currPos < $stackPtrEnd && $currPos !== false);
  141. if ($foundThrows === false) {
  142. return;
  143. }
  144. // Only need one @throws tag for each type of exception thrown.
  145. $thrownExceptions = array_unique($thrownExceptions);
  146. $throwTags = [];
  147. $commentStart = $tokens[$commentEnd]['comment_opener'];
  148. foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
  149. if ($tokens[$tag]['content'] !== '@throws') {
  150. continue;
  151. }
  152. if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
  153. $exception = $tokens[($tag + 2)]['content'];
  154. $space = strpos($exception, ' ');
  155. if ($space !== false) {
  156. $exception = substr($exception, 0, $space);
  157. }
  158. $throwTags[$exception] = true;
  159. }
  160. }
  161. if (empty($throwTags) === true) {
  162. $error = 'Missing @throws tag in function comment';
  163. $phpcsFile->addError($error, $commentEnd, 'Missing');
  164. return;
  165. } else if (empty($thrownExceptions) === true) {
  166. // If token count is zero, it means that only variables are being
  167. // thrown, so we need at least one @throws tag (checked above).
  168. // Nothing more to do.
  169. return;
  170. }
  171. // Make sure @throws tag count matches thrown count.
  172. $thrownCount = (count($thrownExceptions) + $unknownCount);
  173. $tagCount = count($throwTags);
  174. if ($thrownCount !== $tagCount) {
  175. $error = 'Expected %s @throws tag(s) in function comment; %s found';
  176. $data = [
  177. $thrownCount,
  178. $tagCount,
  179. ];
  180. $phpcsFile->addError($error, $commentEnd, 'WrongNumber', $data);
  181. return;
  182. }
  183. foreach ($thrownExceptions as $throw) {
  184. if (isset($throwTags[$throw]) === true) {
  185. continue;
  186. }
  187. foreach ($throwTags as $tag => $ignore) {
  188. if (strrpos($tag, $throw) === (strlen($tag) - strlen($throw))) {
  189. continue 2;
  190. }
  191. }
  192. $error = 'Missing @throws tag for "%s" exception';
  193. $data = [$throw];
  194. $phpcsFile->addError($error, $commentEnd, 'Missing', $data);
  195. }
  196. }//end process()
  197. }//end class