PHP_CodeSniffer
[ class tree: PHP_CodeSniffer ] [ index: PHP_CodeSniffer ] [ all elements ]

Source for file BlockCommentSniff.php

Documentation is available at BlockCommentSniff.php

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

Documentation generated on Mon, 11 Mar 2019 15:27:15 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.