CommentLevelsSniff.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Sniffs\Less;
  7. use PHP_CodeSniffer\Sniffs\Sniff;
  8. use PHP_CodeSniffer\Files\File;
  9. /**
  10. * Class CommentLevelsSniff
  11. *
  12. * First and second level comments must be surrounded by empty lines.
  13. * First, second and third level comments should have two spaces after "//".
  14. * Inline comments should have one space after "//".
  15. *
  16. * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#comments
  17. */
  18. class CommentLevelsSniff implements Sniff
  19. {
  20. const COMMENT_STRING = '//';
  21. const FIRST_LEVEL_COMMENT = '_____________________________________________';
  22. const SECOND_LEVEL_COMMENT = '--';
  23. /**
  24. * @var array
  25. */
  26. protected $levelComments = [
  27. self::FIRST_LEVEL_COMMENT => T_STRING,
  28. self::SECOND_LEVEL_COMMENT => T_DEC,
  29. ];
  30. /**
  31. * A list of tokenizers this sniff supports.
  32. *
  33. * @var array
  34. */
  35. public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS];
  36. /**
  37. * @inheritdoc
  38. */
  39. public function register()
  40. {
  41. return [T_STRING];
  42. }
  43. /**
  44. * @inheritdoc
  45. */
  46. public function process(File $phpcsFile, $stackPtr)
  47. {
  48. $tokens = $phpcsFile->getTokens();
  49. if ((T_STRING !== $tokens[$stackPtr]['code'])
  50. || (self::COMMENT_STRING !== $tokens[$stackPtr]['content'])
  51. || (1 === $tokens[$stackPtr]['line'])
  52. ) {
  53. return;
  54. }
  55. $textInSameLine = $phpcsFile->findPrevious([T_STRING, T_STYLE], $stackPtr - 1);
  56. // is inline comment
  57. if ((false !== $textInSameLine)
  58. && ($tokens[$textInSameLine]['line'] === $tokens[$stackPtr]['line'])
  59. ) {
  60. $this->validateInlineComment($phpcsFile, $stackPtr, $tokens);
  61. return;
  62. }
  63. // validation of levels comments
  64. if (!in_array($tokens[$stackPtr + 1]['content'], [
  65. TokenizerSymbolsInterface::DOUBLE_WHITESPACE,
  66. TokenizerSymbolsInterface::NEW_LINE,
  67. ])
  68. ) {
  69. $phpcsFile->addError('Level\'s comment does not have 2 spaces after "//"', $stackPtr, 'SpacesMissed');
  70. }
  71. if (!$this->isNthLevelComment($phpcsFile, $stackPtr, $tokens)) {
  72. return;
  73. }
  74. if (!$this->checkNthLevelComment($phpcsFile, $stackPtr, $tokens)) {
  75. $phpcsFile->addError(
  76. 'First and second level comments must be surrounded by empty lines',
  77. $stackPtr,
  78. 'SpaceMissed'
  79. );
  80. }
  81. }
  82. /**
  83. * Validate that inline comment responds to given requirements
  84. *
  85. * @param File $phpcsFile
  86. * @param int $stackPtr
  87. * @param array $tokens
  88. * @return bool
  89. */
  90. private function validateInlineComment(File $phpcsFile, $stackPtr, array $tokens)
  91. {
  92. if ($tokens[$stackPtr + 1]['content'] !== TokenizerSymbolsInterface::WHITESPACE) {
  93. $phpcsFile->addError('Inline comment should have 1 space after "//"', $stackPtr, 'SpaceMissedAfter');
  94. }
  95. if ($tokens[$stackPtr - 1]['content'] !== TokenizerSymbolsInterface::WHITESPACE) {
  96. $phpcsFile->addError('Inline comment should have 1 space before "//"', $stackPtr, 'SpaceMissedBefore');
  97. }
  98. }
  99. /**
  100. * Check is it n-th level comment was found
  101. *
  102. * @param File $phpcsFile
  103. * @param int $stackPtr
  104. * @param array $tokens
  105. * @return bool
  106. */
  107. private function isNthLevelComment(File $phpcsFile, $stackPtr, array $tokens)
  108. {
  109. $nthLevelCommentFound = false;
  110. $levelComment = 0;
  111. foreach ($this->levelComments as $code => $comment) {
  112. $levelComment = $phpcsFile->findNext($comment, $stackPtr, null, false, $code);
  113. if (false !== $levelComment) {
  114. $nthLevelCommentFound = true;
  115. break;
  116. }
  117. }
  118. if (false === $nthLevelCommentFound) {
  119. return false;
  120. }
  121. $currentLine = $tokens[$stackPtr]['line'];
  122. $levelCommentLine = $tokens[$levelComment]['line'];
  123. if ($currentLine !== $levelCommentLine) {
  124. return false;
  125. }
  126. return true;
  127. }
  128. /**
  129. * Check is it n-th level comment is correct
  130. *
  131. * @param File $phpcsFile
  132. * @param int $stackPtr
  133. * @param array $tokens
  134. * @return bool
  135. */
  136. private function checkNthLevelComment(File $phpcsFile, $stackPtr, array $tokens)
  137. {
  138. $correct = false;
  139. $nextLine = $phpcsFile->findNext(
  140. T_WHITESPACE,
  141. $stackPtr,
  142. null,
  143. false,
  144. TokenizerSymbolsInterface::NEW_LINE
  145. );
  146. if (false === $nextLine) {
  147. return $correct;
  148. }
  149. if (($tokens[$nextLine]['content'] !== TokenizerSymbolsInterface::NEW_LINE)
  150. || ($tokens[$nextLine + 1]['content'] !== TokenizerSymbolsInterface::NEW_LINE)
  151. ) {
  152. return $correct;
  153. }
  154. $commentLinePtr = $stackPtr;
  155. while ($tokens[$commentLinePtr - 2]['line'] > 1) {
  156. $commentLinePtr = $phpcsFile->findPrevious(T_STRING, $commentLinePtr - 1, null, false, '//');
  157. if (false === $commentLinePtr) {
  158. continue;
  159. }
  160. if (($tokens[$commentLinePtr - 1]['content'] === TokenizerSymbolsInterface::NEW_LINE)
  161. && ($tokens[$commentLinePtr - 2]['content'] === TokenizerSymbolsInterface::NEW_LINE)
  162. ) {
  163. $correct = true;
  164. break;
  165. }
  166. }
  167. return $correct;
  168. }
  169. }