PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php

http://github.com/squizlabs/PHP_CodeSniffer
PHP | 399 lines | 167 code | 23 blank | 209 comment | 19 complexity | f884b49503f341dce395c1c377cba35e MD5 | raw file
  1. <?php
  2. /**
  3. * Verifies that block comments are used appropriately.
  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\Files\File;
  11. use PHP_CodeSniffer\Sniffs\Sniff;
  12. use PHP_CodeSniffer\Util\Tokens;
  13. class BlockCommentSniff implements Sniff
  14. {
  15. /**
  16. * The --tab-width CLI value that is being used.
  17. *
  18. * @var integer
  19. */
  20. private $tabWidth = null;
  21. /**
  22. * Returns an array of tokens this test wants to listen for.
  23. *
  24. * @return array
  25. */
  26. public function register()
  27. {
  28. return [
  29. T_COMMENT,
  30. T_DOC_COMMENT_OPEN_TAG,
  31. ];
  32. }//end register()
  33. /**
  34. * Processes this test, when one of its tokens is encountered.
  35. *
  36. * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being scanned.
  37. * @param int $stackPtr The position of the current token in the
  38. * stack passed in $tokens.
  39. *
  40. * @return void
  41. */
  42. public function process(File $phpcsFile, $stackPtr)
  43. {
  44. if ($this->tabWidth === null) {
  45. if (isset($phpcsFile->config->tabWidth) === false || $phpcsFile->config->tabWidth === 0) {
  46. // We have no idea how wide tabs are, so assume 4 spaces for fixing.
  47. $this->tabWidth = 4;
  48. } else {
  49. $this->tabWidth = $phpcsFile->config->tabWidth;
  50. }
  51. }
  52. $tokens = $phpcsFile->getTokens();
  53. // If it's an inline comment, return.
  54. if (substr($tokens[$stackPtr]['content'], 0, 2) !== '/*') {
  55. return;
  56. }
  57. // If this is a function/class/interface doc block comment, skip it.
  58. // We are only interested in inline doc block comments.
  59. if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) {
  60. $nextToken = $stackPtr;
  61. do {
  62. $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextToken + 1), null, true);
  63. if ($tokens[$nextToken]['code'] === T_ATTRIBUTE) {
  64. $nextToken = $tokens[$nextToken]['attribute_closer'];
  65. continue;
  66. }
  67. break;
  68. } while (true);
  69. $ignore = [
  70. T_CLASS => true,
  71. T_INTERFACE => true,
  72. T_TRAIT => true,
  73. T_FUNCTION => true,
  74. T_PUBLIC => true,
  75. T_PRIVATE => true,
  76. T_FINAL => true,
  77. T_PROTECTED => true,
  78. T_STATIC => true,
  79. T_ABSTRACT => true,
  80. T_CONST => true,
  81. T_VAR => true,
  82. ];
  83. if (isset($ignore[$tokens[$nextToken]['code']]) === true) {
  84. return;
  85. }
  86. $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
  87. if ($tokens[$prevToken]['code'] === T_OPEN_TAG) {
  88. return;
  89. }
  90. $error = 'Block comments must be started with /*';
  91. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStart');
  92. if ($fix === true) {
  93. $phpcsFile->fixer->replaceToken($stackPtr, '/*');
  94. }
  95. $end = $tokens[$stackPtr]['comment_closer'];
  96. if ($tokens[$end]['content'] !== '*/') {
  97. $error = 'Block comments must be ended with */';
  98. $fix = $phpcsFile->addFixableError($error, $end, 'WrongEnd');
  99. if ($fix === true) {
  100. $phpcsFile->fixer->replaceToken($end, '*/');
  101. }
  102. }
  103. return;
  104. }//end if
  105. $commentLines = [$stackPtr];
  106. $nextComment = $stackPtr;
  107. $lastLine = $tokens[$stackPtr]['line'];
  108. $commentString = $tokens[$stackPtr]['content'];
  109. // Construct the comment into an array.
  110. while (($nextComment = $phpcsFile->findNext(T_WHITESPACE, ($nextComment + 1), null, true)) !== false) {
  111. if ($tokens[$nextComment]['code'] !== $tokens[$stackPtr]['code']
  112. && isset(Tokens::$phpcsCommentTokens[$tokens[$nextComment]['code']]) === false
  113. ) {
  114. // Found the next bit of code.
  115. break;
  116. }
  117. if (($tokens[$nextComment]['line'] - 1) !== $lastLine) {
  118. // Not part of the block.
  119. break;
  120. }
  121. $lastLine = $tokens[$nextComment]['line'];
  122. $commentLines[] = $nextComment;
  123. $commentString .= $tokens[$nextComment]['content'];
  124. if ($tokens[$nextComment]['code'] === T_DOC_COMMENT_CLOSE_TAG
  125. || substr($tokens[$nextComment]['content'], -2) === '*/'
  126. ) {
  127. break;
  128. }
  129. }//end while
  130. $commentText = str_replace($phpcsFile->eolChar, '', $commentString);
  131. $commentText = trim($commentText, "/* \t");
  132. if ($commentText === '') {
  133. $error = 'Empty block comment not allowed';
  134. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty');
  135. if ($fix === true) {
  136. $phpcsFile->fixer->beginChangeset();
  137. $phpcsFile->fixer->replaceToken($stackPtr, '');
  138. $lastToken = array_pop($commentLines);
  139. for ($i = ($stackPtr + 1); $i <= $lastToken; $i++) {
  140. $phpcsFile->fixer->replaceToken($i, '');
  141. }
  142. $phpcsFile->fixer->endChangeset();
  143. }
  144. return;
  145. }
  146. if (count($commentLines) === 1) {
  147. $error = 'Single line block comment not allowed; use inline ("// text") comment instead';
  148. // Only fix comments when they are the last token on a line.
  149. $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
  150. if ($tokens[$stackPtr]['line'] !== $tokens[$nextNonEmpty]['line']) {
  151. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SingleLine');
  152. if ($fix === true) {
  153. $comment = '// '.$commentText.$phpcsFile->eolChar;
  154. $phpcsFile->fixer->replaceToken($stackPtr, $comment);
  155. }
  156. } else {
  157. $phpcsFile->addError($error, $stackPtr, 'SingleLine');
  158. }
  159. return;
  160. }
  161. $content = trim($tokens[$stackPtr]['content']);
  162. if ($content !== '/*' && $content !== '/**') {
  163. $error = 'Block comment text must start on a new line';
  164. $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoNewLine');
  165. if ($fix === true) {
  166. $indent = '';
  167. if ($tokens[($stackPtr - 1)]['code'] === T_WHITESPACE) {
  168. if (isset($tokens[($stackPtr - 1)]['orig_content']) === true) {
  169. $indent = $tokens[($stackPtr - 1)]['orig_content'];
  170. } else {
  171. $indent = $tokens[($stackPtr - 1)]['content'];
  172. }
  173. }
  174. $comment = preg_replace(
  175. '/^(\s*\/\*\*?)/',
  176. '$1'.$phpcsFile->eolChar.$indent,
  177. $tokens[$stackPtr]['content'],
  178. 1
  179. );
  180. $phpcsFile->fixer->replaceToken($stackPtr, $comment);
  181. }
  182. return;
  183. }//end if
  184. $starColumn = $tokens[$stackPtr]['column'];
  185. $hasStars = false;
  186. // Make sure first line isn't blank.
  187. if (trim($tokens[$commentLines[1]]['content']) === '') {
  188. $error = 'Empty line not allowed at start of comment';
  189. $fix = $phpcsFile->addFixableError($error, $commentLines[1], 'HasEmptyLine');
  190. if ($fix === true) {
  191. $phpcsFile->fixer->replaceToken($commentLines[1], '');
  192. }
  193. } else {
  194. // Check indentation of first line.
  195. $content = $tokens[$commentLines[1]]['content'];
  196. $commentText = ltrim($content);
  197. $leadingSpace = (strlen($content) - strlen($commentText));
  198. $expected = ($starColumn + 3);
  199. if ($commentText[0] === '*') {
  200. $expected = $starColumn;
  201. $hasStars = true;
  202. }
  203. if ($leadingSpace !== $expected) {
  204. $expectedTxt = $expected.' space';
  205. if ($expected !== 1) {
  206. $expectedTxt .= 's';
  207. }
  208. $data = [
  209. $expectedTxt,
  210. $leadingSpace,
  211. ];
  212. $error = 'First line of comment not aligned correctly; expected %s but found %s';
  213. $fix = $phpcsFile->addFixableError($error, $commentLines[1], 'FirstLineIndent', $data);
  214. if ($fix === true) {
  215. if (isset($tokens[$commentLines[1]]['orig_content']) === true
  216. && $tokens[$commentLines[1]]['orig_content'][0] === "\t"
  217. ) {
  218. // Line is indented using tabs.
  219. $padding = str_repeat("\t", floor($expected / $this->tabWidth));
  220. $padding .= str_repeat(' ', ($expected % $this->tabWidth));
  221. } else {
  222. $padding = str_repeat(' ', $expected);
  223. }
  224. $phpcsFile->fixer->replaceToken($commentLines[1], $padding.$commentText);
  225. }
  226. }//end if
  227. if (preg_match('/^\p{Ll}/u', $commentText) === 1) {
  228. $error = 'Block comments must start with a capital letter';
  229. $phpcsFile->addError($error, $commentLines[1], 'NoCapital');
  230. }
  231. }//end if
  232. // Check that each line of the comment is indented past the star.
  233. foreach ($commentLines as $line) {
  234. // First and last lines (comment opener and closer) are handled separately.
  235. if ($line === $commentLines[(count($commentLines) - 1)] || $line === $commentLines[0]) {
  236. continue;
  237. }
  238. // First comment line was handled above.
  239. if ($line === $commentLines[1]) {
  240. continue;
  241. }
  242. // If it's empty, continue.
  243. if (trim($tokens[$line]['content']) === '') {
  244. continue;
  245. }
  246. $commentText = ltrim($tokens[$line]['content']);
  247. $leadingSpace = (strlen($tokens[$line]['content']) - strlen($commentText));
  248. $expected = ($starColumn + 3);
  249. if ($commentText[0] === '*') {
  250. $expected = $starColumn;
  251. $hasStars = true;
  252. }
  253. if ($leadingSpace < $expected) {
  254. $expectedTxt = $expected.' space';
  255. if ($expected !== 1) {
  256. $expectedTxt .= 's';
  257. }
  258. $data = [
  259. $expectedTxt,
  260. $leadingSpace,
  261. ];
  262. $error = 'Comment line indented incorrectly; expected at least %s but found %s';
  263. $fix = $phpcsFile->addFixableError($error, $line, 'LineIndent', $data);
  264. if ($fix === true) {
  265. if (isset($tokens[$line]['orig_content']) === true
  266. && $tokens[$line]['orig_content'][0] === "\t"
  267. ) {
  268. // Line is indented using tabs.
  269. $padding = str_repeat("\t", floor($expected / $this->tabWidth));
  270. $padding .= str_repeat(' ', ($expected % $this->tabWidth));
  271. } else {
  272. $padding = str_repeat(' ', $expected);
  273. }
  274. $phpcsFile->fixer->replaceToken($line, $padding.$commentText);
  275. }
  276. }//end if
  277. }//end foreach
  278. // Finally, test the last line is correct.
  279. $lastIndex = (count($commentLines) - 1);
  280. $content = $tokens[$commentLines[$lastIndex]]['content'];
  281. $commentText = ltrim($content);
  282. if ($commentText !== '*/' && $commentText !== '**/') {
  283. $error = 'Comment closer must be on a new line';
  284. $phpcsFile->addError($error, $commentLines[$lastIndex], 'CloserSameLine');
  285. } else {
  286. $leadingSpace = (strlen($content) - strlen($commentText));
  287. $expected = ($starColumn - 1);
  288. if ($hasStars === true) {
  289. $expected = $starColumn;
  290. }
  291. if ($leadingSpace !== $expected) {
  292. $expectedTxt = $expected.' space';
  293. if ($expected !== 1) {
  294. $expectedTxt .= 's';
  295. }
  296. $data = [
  297. $expectedTxt,
  298. $leadingSpace,
  299. ];
  300. $error = 'Last line of comment aligned incorrectly; expected %s but found %s';
  301. $fix = $phpcsFile->addFixableError($error, $commentLines[$lastIndex], 'LastLineIndent', $data);
  302. if ($fix === true) {
  303. if (isset($tokens[$line]['orig_content']) === true
  304. && $tokens[$line]['orig_content'][0] === "\t"
  305. ) {
  306. // Line is indented using tabs.
  307. $padding = str_repeat("\t", floor($expected / $this->tabWidth));
  308. $padding .= str_repeat(' ', ($expected % $this->tabWidth));
  309. } else {
  310. $padding = str_repeat(' ', $expected);
  311. }
  312. $phpcsFile->fixer->replaceToken($commentLines[$lastIndex], $padding.$commentText);
  313. }
  314. }//end if
  315. }//end if
  316. // Check that the lines before and after this comment are blank.
  317. $contentBefore = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
  318. if ((isset($tokens[$contentBefore]['scope_closer']) === true
  319. && $tokens[$contentBefore]['scope_opener'] === $contentBefore)
  320. || $tokens[$contentBefore]['code'] === T_OPEN_TAG
  321. || $tokens[$contentBefore]['code'] === T_OPEN_TAG_WITH_ECHO
  322. ) {
  323. if (($tokens[$stackPtr]['line'] - $tokens[$contentBefore]['line']) !== 1) {
  324. $error = 'Empty line not required before block comment';
  325. $phpcsFile->addError($error, $stackPtr, 'HasEmptyLineBefore');
  326. }
  327. } else {
  328. if (($tokens[$stackPtr]['line'] - $tokens[$contentBefore]['line']) < 2) {
  329. $error = 'Empty line required before block comment';
  330. $phpcsFile->addError($error, $stackPtr, 'NoEmptyLineBefore');
  331. }
  332. }
  333. $commentCloser = $commentLines[$lastIndex];
  334. $contentAfter = $phpcsFile->findNext(T_WHITESPACE, ($commentCloser + 1), null, true);
  335. if ($contentAfter !== false && ($tokens[$contentAfter]['line'] - $tokens[$commentCloser]['line']) < 2) {
  336. $error = 'Empty line required after block comment';
  337. $phpcsFile->addError($error, $commentCloser, 'NoEmptyLineAfter');
  338. }
  339. }//end process()
  340. }//end class