PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/script/lib/PHP/CodeSniffer/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php

https://bitbucket.org/chamilo/chamilo-dev/
PHP | 376 lines | 235 code | 50 blank | 91 comment | 43 complexity | 6327e260e9798e3223fbc145de1dd034 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT
  1. <?php
  2. /**
  3. * Parses and verifies the class doc comment.
  4. *
  5. * PHP version 5
  6. *
  7. * @category PHP
  8. * @package PHP_CodeSniffer
  9. * @author Greg Sherwood <gsherwood@squiz.net>
  10. * @author Marc McIntyre <mmcintyre@squiz.net>
  11. * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
  12. * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
  13. * @version CVS: $Id: ClassCommentSniff.php 292513 2009-12-23 00:41:20Z squiz $
  14. * @link http://pear.php.net/package/PHP_CodeSniffer
  15. */
  16. if (class_exists('PHP_CodeSniffer_CommentParser_ClassCommentParser', true) === false)
  17. {
  18. throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CommentParser_ClassCommentParser not found');
  19. }
  20. /**
  21. * Parses and verifies the class doc comment.
  22. *
  23. * Verifies that :
  24. * <ul>
  25. * <li>A class doc comment exists.</li>
  26. * <li>There is exactly one blank line before the class comment.</li>
  27. * <li>Short description ends with a full stop.</li>
  28. * <li>There is a blank line after the short description.</li>
  29. * <li>Each paragraph of the long description ends with a full stop.</li>
  30. * <li>There is a blank line between the description and the tags.</li>
  31. * <li>Check the format of the since tag (x.x.x).</li>
  32. * </ul>
  33. *
  34. * @category PHP
  35. * @package PHP_CodeSniffer
  36. * @author Greg Sherwood <gsherwood@squiz.net>
  37. * @author Marc McIntyre <mmcintyre@squiz.net>
  38. * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
  39. * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
  40. * @version Release: 1.2.2
  41. * @link http://pear.php.net/package/PHP_CodeSniffer
  42. */
  43. class Squiz_Sniffs_Commenting_ClassCommentSniff implements PHP_CodeSniffer_Sniff
  44. {
  45. /**
  46. * Returns an array of tokens this test wants to listen for.
  47. *
  48. * @return array
  49. */
  50. public function register()
  51. {
  52. return array(T_CLASS);
  53. } //end register()
  54. /**
  55. * Processes this test, when one of its tokens is encountered.
  56. *
  57. * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
  58. * @param int $stackPtr The position of the current token
  59. * in the stack passed in $tokens.
  60. *
  61. * @return void
  62. */
  63. public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
  64. {
  65. $this->currentFile = $phpcsFile;
  66. $tokens = $phpcsFile->getTokens();
  67. $find = array(T_ABSTRACT, T_WHITESPACE, T_FINAL);
  68. // Extract the class comment docblock.
  69. $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
  70. if ($commentEnd !== false && $tokens[$commentEnd]['code'] === T_COMMENT)
  71. {
  72. $phpcsFile->addError('You must use "/**" style comments for a class comment', $stackPtr);
  73. return;
  74. }
  75. else
  76. if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT)
  77. {
  78. $phpcsFile->addError('Missing class doc comment', $stackPtr);
  79. return;
  80. }
  81. $commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1);
  82. $commentNext = $phpcsFile->findPrevious(T_WHITESPACE, ($commentEnd + 1), $stackPtr, false, $phpcsFile->eolChar);
  83. // Distinguish file and class comment.
  84. $prevClassToken = $phpcsFile->findPrevious(T_CLASS, ($stackPtr - 1));
  85. if ($prevClassToken === false)
  86. {
  87. // This is the first class token in this file, need extra checks.
  88. $prevNonComment = $phpcsFile->findPrevious(T_DOC_COMMENT, ($commentStart - 1), null, true);
  89. if ($prevNonComment !== false)
  90. {
  91. $prevComment = $phpcsFile->findPrevious(T_DOC_COMMENT, ($prevNonComment - 1));
  92. if ($prevComment === false)
  93. {
  94. // There is only 1 doc comment between open tag and class token.
  95. $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($commentEnd + 1), $stackPtr, false, $phpcsFile->eolChar);
  96. if ($newlineToken !== false)
  97. {
  98. $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($newlineToken + 1), $stackPtr, false, $phpcsFile->eolChar);
  99. if ($newlineToken !== false)
  100. {
  101. // Blank line between the class and the doc block.
  102. // The doc block is most likely a file comment.
  103. $phpcsFile->addError('Missing class doc comment', ($stackPtr + 1));
  104. return;
  105. }
  106. } //end if
  107. } //end if
  108. // Exactly one blank line before the class comment.
  109. $prevTokenEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($commentStart - 1), null, true);
  110. if ($prevTokenEnd !== false)
  111. {
  112. $blankLineBefore = 0;
  113. for($i = ($prevTokenEnd + 1); $i < $commentStart; $i ++)
  114. {
  115. if ($tokens[$i]['code'] === T_WHITESPACE && $tokens[$i]['content'] === $phpcsFile->eolChar)
  116. {
  117. $blankLineBefore ++;
  118. }
  119. }
  120. if ($blankLineBefore !== 2)
  121. {
  122. $error = 'There must be exactly one blank line before the class comment';
  123. $phpcsFile->addError($error, ($commentStart - 1));
  124. }
  125. }
  126. } //end if
  127. } //end if
  128. $commentString = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1));
  129. // Parse the class comment docblock.
  130. try
  131. {
  132. $this->commentParser = new PHP_CodeSniffer_CommentParser_ClassCommentParser($commentString, $phpcsFile);
  133. $this->commentParser->parse();
  134. }
  135. catch (PHP_CodeSniffer_CommentParser_ParserException $e)
  136. {
  137. $line = ($e->getLineWithinComment() + $commentStart);
  138. $phpcsFile->addError($e->getMessage(), $line);
  139. return;
  140. }
  141. $comment = $this->commentParser->getComment();
  142. if (is_null($comment) === true)
  143. {
  144. $error = 'Class doc comment is empty';
  145. $phpcsFile->addError($error, $commentStart);
  146. return;
  147. }
  148. // The first line of the comment should just be the /** code.
  149. $eolPos = strpos($commentString, $phpcsFile->eolChar);
  150. $firstLine = substr($commentString, 0, $eolPos);
  151. if ($firstLine !== '/**')
  152. {
  153. $error = 'The open comment tag must be the only content on the line';
  154. $phpcsFile->addError($error, $commentStart);
  155. }
  156. // Check for a comment description.
  157. $short = rtrim($comment->getShortComment(), $phpcsFile->eolChar);
  158. if (trim($short) === '')
  159. {
  160. $error = 'Missing short description in class doc comment';
  161. $phpcsFile->addError($error, $commentStart);
  162. return;
  163. }
  164. // No extra newline before short description.
  165. $newlineCount = 0;
  166. $newlineSpan = strspn($short, $phpcsFile->eolChar);
  167. if ($short !== '' && $newlineSpan > 0)
  168. {
  169. $line = ($newlineSpan > 1) ? 'newlines' : 'newline';
  170. $error = "Extra $line found before class comment short description";
  171. $phpcsFile->addError($error, ($commentStart + 1));
  172. }
  173. $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1);
  174. // Exactly one blank line between short and long description.
  175. $long = $comment->getLongComment();
  176. if (empty($long) === false)
  177. {
  178. $between = $comment->getWhiteSpaceBetween();
  179. $newlineBetween = substr_count($between, $phpcsFile->eolChar);
  180. if ($newlineBetween !== 2)
  181. {
  182. $error = 'There must be exactly one blank line between descriptions in class comment';
  183. $phpcsFile->addError($error, ($commentStart + $newlineCount + 1));
  184. }
  185. $newlineCount += $newlineBetween;
  186. $testLong = trim($long);
  187. if (preg_match('|[A-Z]|', $testLong[0]) === 0)
  188. {
  189. $error = 'Class comment long description must start with a capital letter';
  190. $phpcsFile->addError($error, ($commentStart + $newlineCount));
  191. }
  192. }
  193. // Exactly one blank line before tags.
  194. $tags = $this->commentParser->getTagOrders();
  195. if (count($tags) > 1)
  196. {
  197. $newlineSpan = $comment->getNewlineAfter();
  198. if ($newlineSpan !== 2)
  199. {
  200. $error = 'There must be exactly one blank line before the tags in class comment';
  201. if ($long !== '')
  202. {
  203. $newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1);
  204. }
  205. $phpcsFile->addError($error, ($commentStart + $newlineCount));
  206. $short = rtrim($short, $phpcsFile->eolChar . ' ');
  207. }
  208. }
  209. // Short description must be single line and end with a full stop.
  210. $testShort = trim($short);
  211. $lastChar = $testShort[(strlen($testShort) - 1)];
  212. if (substr_count($testShort, $phpcsFile->eolChar) !== 0)
  213. {
  214. $error = 'Class comment short description must be on a single line';
  215. $phpcsFile->addError($error, ($commentStart + 1));
  216. }
  217. if (preg_match('|[A-Z]|', $testShort[0]) === 0)
  218. {
  219. $error = 'Class comment short description must start with a capital letter';
  220. $phpcsFile->addError($error, ($commentStart + 1));
  221. }
  222. if ($lastChar !== '.')
  223. {
  224. $error = 'Class comment short description must end with a full stop';
  225. $phpcsFile->addError($error, ($commentStart + 1));
  226. }
  227. // Check for unknown/deprecated tags.
  228. $unknownTags = $this->commentParser->getUnknown();
  229. foreach ($unknownTags as $errorTag)
  230. {
  231. $error = "@$errorTag[tag] tag is not allowed in class comment";
  232. $phpcsFile->addWarning($error, ($commentStart + $errorTag['line']));
  233. return;
  234. }
  235. // Check each tag.
  236. $this->processTags($commentStart, $commentEnd);
  237. } //end process()
  238. /**
  239. * Processes each required or optional tag.
  240. *
  241. * @param int $commentStart The position in the stack where the comment started.
  242. * @param int $commentEnd The position in the stack where the comment ended.
  243. *
  244. * @return void
  245. */
  246. protected function processTags($commentStart, $commentEnd)
  247. {
  248. $foundTags = $this->commentParser->getTagOrders();
  249. // Other tags found.
  250. foreach ($foundTags as $tagName)
  251. {
  252. if ($tagName !== 'comment' && $tagName !== 'since')
  253. {
  254. $error = 'Only @since tag is allowed in class comment';
  255. $this->currentFile->addWarning($error, $commentEnd);
  256. break;
  257. }
  258. }
  259. // Since tag missing.
  260. if (in_array('since', $foundTags) === false)
  261. {
  262. $error = 'Missing @since tag in class comment';
  263. $this->currentFile->addError($error, $commentEnd);
  264. return;
  265. }
  266. // Get the line number for current tag.
  267. $since = $this->commentParser->getSince();
  268. if (is_null($since) === true || empty($since) === true)
  269. {
  270. return;
  271. }
  272. $errorPos = ($commentStart + $since->getLine());
  273. // Make sure there is no duplicate tag.
  274. $foundIndexes = array_keys($foundTags, 'since');
  275. if (count($foundIndexes) > 1)
  276. {
  277. $error = 'Only 1 @since tag is allowed in class comment';
  278. $this->currentFile->addError($error, $errorPos);
  279. }
  280. // Check spacing.
  281. if ($since->getContent() !== '')
  282. {
  283. $spacing = substr_count($since->getWhitespaceBeforeContent(), ' ');
  284. if ($spacing !== 1)
  285. {
  286. $error = "Expected 1 space but found $spacing before version number in @since tag";
  287. $this->currentFile->addError($error, $errorPos);
  288. }
  289. }
  290. // Check content.
  291. $this->processSince($errorPos);
  292. } //end processTags()
  293. /**
  294. * Processes the since tag.
  295. *
  296. * The since tag must have the exact keyword 'release_version'
  297. * or is in the form x.x.x
  298. *
  299. * @param int $errorPos The line number where the error occurs.
  300. *
  301. * @return void
  302. */
  303. protected function processSince($errorPos)
  304. {
  305. $since = $this->commentParser->getSince();
  306. if ($since !== null)
  307. {
  308. $content = $since->getContent();
  309. if (empty($content) === true)
  310. {
  311. $error = 'Content missing for @since tag in class comment';
  312. $this->currentFile->addError($error, $errorPos);
  313. }
  314. else
  315. if ($content !== '%release_version%')
  316. {
  317. if (preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)/', $content) === 0)
  318. {
  319. $error = 'Expected version number to be in the form x.x.x in @since tag';
  320. $this->currentFile->addError($error, $errorPos);
  321. }
  322. }
  323. }
  324. } //end processSince()
  325. } //end class
  326. ?>