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

Source for file DocCommentSniff.php

Documentation is available at DocCommentSniff.php

  1. <?php
  2. /**
  3.  * Ensures doc blocks follow basic formatting.
  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\Generic\Sniffs\Commenting;
  11.  
  12. use PHP_CodeSniffer\Sniffs\Sniff;
  13. use PHP_CodeSniffer\Files\File;
  14.  
  15. class DocCommentSniff implements Sniff
  16. {
  17.  
  18.     /**
  19.      * A list of tokenizers this sniff supports.
  20.      *
  21.      * @var array 
  22.      */
  23.     public $supportedTokenizers = array(
  24.                                    'PHP',
  25.                                    'JS',
  26.                                   );
  27.  
  28.  
  29.     /**
  30.      * Returns an array of tokens this test wants to listen for.
  31.      *
  32.      * @return array 
  33.      */
  34.     public function register()
  35.     {
  36.         return array(T_DOC_COMMENT_OPEN_TAG);
  37.  
  38.     }//end register()
  39.  
  40.  
  41.     /**
  42.      * Processes this test, when one of its tokens is encountered.
  43.      *
  44.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  45.      * @param int                         $stackPtr  The position of the current token
  46.      *                                                in the stack passed in $tokens.
  47.      *
  48.      * @return void 
  49.      */
  50.     public function process(File $phpcsFile$stackPtr)
  51.     {
  52.         $tokens       $phpcsFile->getTokens();
  53.         $commentStart $stackPtr;
  54.         $commentEnd   $tokens[$stackPtr]['comment_closer'];
  55.  
  56.         $empty = array(
  57.                   T_DOC_COMMENT_WHITESPACE,
  58.                   T_DOC_COMMENT_STAR,
  59.                  );
  60.  
  61.         $short $phpcsFile->findNext($empty($stackPtr + 1)$commentEndtrue);
  62.         if ($short === false{
  63.             // No content at all.
  64.             $error 'Doc comment is empty';
  65.             $phpcsFile->addError($error$stackPtr'Empty');
  66.             return;
  67.         }
  68.  
  69.         // The first line of the comment should just be the /** code.
  70.         if ($tokens[$short]['line'=== $tokens[$stackPtr]['line']{
  71.             $error 'The open comment tag must be the only content on the line';
  72.             $fix   $phpcsFile->addFixableError($error$stackPtr'ContentAfterOpen');
  73.             if ($fix === true{
  74.                 $phpcsFile->fixer->beginChangeset();
  75.                 $phpcsFile->fixer->addNewline($stackPtr);
  76.                 $phpcsFile->fixer->addContentBefore($short'* ');
  77.                 $phpcsFile->fixer->endChangeset();
  78.             }
  79.         }
  80.  
  81.         // The last line of the comment should just be the */ code.
  82.         $prev $phpcsFile->findPrevious($empty($commentEnd - 1)$stackPtrtrue);
  83.         if ($tokens[$prev]['line'=== $tokens[$commentEnd]['line']{
  84.             $error 'The close comment tag must be the only content on the line';
  85.             $fix   $phpcsFile->addFixableError($error$commentEnd'ContentBeforeClose');
  86.             if ($fix === true{
  87.                 $phpcsFile->fixer->addNewlineBefore($commentEnd);
  88.             }
  89.         }
  90.  
  91.         // Check for additional blank lines at the end of the comment.
  92.         if ($tokens[$prev]['line'($tokens[$commentEnd]['line'- 1)) {
  93.             $error 'Additional blank lines found at end of doc comment';
  94.             $fix   $phpcsFile->addFixableError($error$commentEnd'SpacingAfter');
  95.             if ($fix === true{
  96.                 $phpcsFile->fixer->beginChangeset();
  97.                 for ($i ($prev + 1)$i $commentEnd$i++{
  98.                     if ($tokens[($i + 1)]['line'=== $tokens[$commentEnd]['line']{
  99.                         break;
  100.                     }
  101.  
  102.                     $phpcsFile->fixer->replaceToken($i'');
  103.                 }
  104.  
  105.                 $phpcsFile->fixer->endChangeset();
  106.             }
  107.         }
  108.  
  109.         // Check for a comment description.
  110.         if ($tokens[$short]['code'!== T_DOC_COMMENT_STRING{
  111.             $error 'Missing short description in doc comment';
  112.             $phpcsFile->addError($error$stackPtr'MissingShort');
  113.             return;
  114.         }
  115.  
  116.         // No extra newline before short description.
  117.         if ($tokens[$short]['line'!== ($tokens[$stackPtr]['line'+ 1)) {
  118.             $error 'Doc comment short description must be on the first line';
  119.             $fix   $phpcsFile->addFixableError($error$short'SpacingBeforeShort');
  120.             if ($fix === true{
  121.                 $phpcsFile->fixer->beginChangeset();
  122.                 for ($i $stackPtr$i $short$i++{
  123.                     if ($tokens[$i]['line'=== $tokens[$stackPtr]['line']{
  124.                         continue;
  125.                     else if ($tokens[$i]['line'=== $tokens[$short]['line']{
  126.                         break;
  127.                     }
  128.  
  129.                     $phpcsFile->fixer->replaceToken($i'');
  130.                 }
  131.  
  132.                 $phpcsFile->fixer->endChangeset();
  133.             }
  134.         }
  135.  
  136.         // Account for the fact that a short description might cover
  137.         // multiple lines.
  138.         $shortContent $tokens[$short]['content'];
  139.         $shortEnd     $short;
  140.         for ($i ($short + 1)$i $commentEnd$i++{
  141.             if ($tokens[$i]['code'=== T_DOC_COMMENT_STRING{
  142.                 if ($tokens[$i]['line'=== ($tokens[$shortEnd]['line'+ 1)) {
  143.                     $shortContent .= $tokens[$i]['content'];
  144.                     $shortEnd      $i;
  145.                 else {
  146.                     break;
  147.                 }
  148.             }
  149.         }
  150.  
  151.         if (preg_match('/^\p{Ll}/u'$shortContent=== 1{
  152.             $error 'Doc comment short description must start with a capital letter';
  153.             $phpcsFile->addError($error$short'ShortNotCapital');
  154.         }
  155.  
  156.         $long $phpcsFile->findNext($empty($shortEnd + 1)($commentEnd - 1)true);
  157.         if ($long !== false && $tokens[$long]['code'=== T_DOC_COMMENT_STRING{
  158.             if ($tokens[$long]['line'!== ($tokens[$shortEnd]['line'+ 2)) {
  159.                 $error 'There must be exactly one blank line between descriptions in a doc comment';
  160.                 $fix   $phpcsFile->addFixableError($error$long'SpacingBetween');
  161.                 if ($fix === true{
  162.                     $phpcsFile->fixer->beginChangeset();
  163.                     for ($i ($shortEnd + 1)$i $long$i++{
  164.                         if ($tokens[$i]['line'=== $tokens[$shortEnd]['line']{
  165.                             continue;
  166.                         else if ($tokens[$i]['line'=== ($tokens[$long]['line'- 1)) {
  167.                             break;
  168.                         }
  169.  
  170.                         $phpcsFile->fixer->replaceToken($i'');
  171.                     }
  172.  
  173.                     $phpcsFile->fixer->endChangeset();
  174.                 }
  175.             }
  176.  
  177.             if (preg_match('/^\p{Ll}/u'$tokens[$long]['content']=== 1{
  178.                 $error 'Doc comment long description must start with a capital letter';
  179.                 $phpcsFile->addError($error$long'LongNotCapital');
  180.             }
  181.         }//end if
  182.  
  183.         if (empty($tokens[$commentStart]['comment_tags']=== true{
  184.             // No tags in the comment.
  185.             return;
  186.         }
  187.  
  188.         $firstTag $tokens[$commentStart]['comment_tags'][0];
  189.         $prev     $phpcsFile->findPrevious($empty($firstTag - 1)$stackPtrtrue);
  190.         if ($tokens[$firstTag]['line'!== ($tokens[$prev]['line'+ 2)) {
  191.             $error 'There must be exactly one blank line before the tags in a doc comment';
  192.             $fix   $phpcsFile->addFixableError($error$firstTag'SpacingBeforeTags');
  193.             if ($fix === true{
  194.                 $phpcsFile->fixer->beginChangeset();
  195.                 for ($i ($prev + 1)$i $firstTag$i++{
  196.                     if ($tokens[$i]['line'=== $tokens[$firstTag]['line']{
  197.                         break;
  198.                     }
  199.  
  200.                     $phpcsFile->fixer->replaceToken($i'');
  201.                 }
  202.  
  203.                 $indent str_repeat(' '$tokens[$stackPtr]['column']);
  204.                 $phpcsFile->fixer->addContent($prev$phpcsFile->eolChar.$indent.'*'.$phpcsFile->eolChar);
  205.                 $phpcsFile->fixer->endChangeset();
  206.             }
  207.         }
  208.  
  209.         // Break out the tags into groups and check alignment within each.
  210.         // A tag group is one where there are no blank lines between tags.
  211.         // The param tag group is special as it requires all @param tags to be inside.
  212.         $tagGroups    = array();
  213.         $groupid      = 0;
  214.         $paramGroupid = null;
  215.         foreach ($tokens[$commentStart]['comment_tags'as $pos => $tag{
  216.             if ($pos > 0{
  217.                 $prev $phpcsFile->findPrevious(
  218.                     T_DOC_COMMENT_STRING,
  219.                     ($tag - 1),
  220.                     $tokens[$commentStart]['comment_tags'][($pos - 1)]
  221.                 );
  222.  
  223.                 if ($prev === false{
  224.                     $prev $tokens[$commentStart]['comment_tags'][($pos - 1)];
  225.                 }
  226.  
  227.                 if ($tokens[$prev]['line'!== ($tokens[$tag]['line'- 1)) {
  228.                     $groupid++;
  229.                 }
  230.             }
  231.  
  232.             if ($tokens[$tag]['content'=== '@param'{
  233.                 if (($paramGroupid === null
  234.                     && empty($tagGroups[$groupid]=== false)
  235.                     || ($paramGroupid !== null
  236.                     && $paramGroupid !== $groupid)
  237.                 {
  238.                     $error 'Parameter tags must be grouped together in a doc comment';
  239.                     $phpcsFile->addError($error$tag'ParamGroup');
  240.                 }
  241.  
  242.                 if ($paramGroupid === null{
  243.                     $paramGroupid $groupid;
  244.                 }
  245.             else if ($groupid === $paramGroupid{
  246.                 $error 'Tag cannot be grouped with parameter tags in a doc comment';
  247.                 $phpcsFile->addError($error$tag'NonParamGroup');
  248.             }//end if
  249.  
  250.             $tagGroups[$groupid][$tag;
  251.         }//end foreach
  252.  
  253.         foreach ($tagGroups as $group{
  254.             $maxLength = 0;
  255.             $paddings  = array();
  256.             foreach ($group as $pos => $tag{
  257.                 $tagLength strlen($tokens[$tag]['content']);
  258.                 if ($tagLength $maxLength{
  259.                     $maxLength $tagLength;
  260.                 }
  261.  
  262.                 // Check for a value. No value means no padding needed.
  263.                 $string $phpcsFile->findNext(T_DOC_COMMENT_STRING$tag$commentEnd);
  264.                 if ($string !== false && $tokens[$string]['line'=== $tokens[$tag]['line']{
  265.                     $paddings[$tagstrlen($tokens[($tag + 1)]['content']);
  266.                 }
  267.             }
  268.  
  269.             // Check that there was single blank line after the tag block
  270.             // but account for a multi-line tag comments.
  271.             $lastTag $group[$pos];
  272.             $next    $phpcsFile->findNext(T_DOC_COMMENT_TAG($lastTag + 3)$commentEnd);
  273.             if ($next !== false{
  274.                 $prev $phpcsFile->findPrevious(array(T_DOC_COMMENT_TAGT_DOC_COMMENT_STRING)($next - 1)$commentStart);
  275.                 if ($tokens[$next]['line'!== ($tokens[$prev]['line'+ 2)) {
  276.                     $error 'There must be a single blank line after a tag group';
  277.                     $fix   $phpcsFile->addFixableError($error$lastTag'SpacingAfterTagGroup');
  278.                     if ($fix === true{
  279.                         $phpcsFile->fixer->beginChangeset();
  280.                         for ($i ($prev + 1)$i $next$i++{
  281.                             if ($tokens[$i]['line'=== $tokens[$next]['line']{
  282.                                 break;
  283.                             }
  284.  
  285.                             $phpcsFile->fixer->replaceToken($i'');
  286.                         }
  287.  
  288.                         $indent str_repeat(' '$tokens[$stackPtr]['column']);
  289.                         $phpcsFile->fixer->addContent($prev$phpcsFile->eolChar.$indent.'*'.$phpcsFile->eolChar);
  290.                         $phpcsFile->fixer->endChangeset();
  291.                     }
  292.                 }
  293.             }//end if
  294.  
  295.             // Now check paddings.
  296.             foreach ($paddings as $tag => $padding{
  297.                 $required ($maxLength strlen($tokens[$tag]['content']+ 1);
  298.  
  299.                 if ($padding !== $required{
  300.                     $error 'Tag value indented incorrectly; expected %s spaces but found %s';
  301.                     $data  = array(
  302.                               $required,
  303.                               $padding,
  304.                              );
  305.  
  306.                     $fix $phpcsFile->addFixableError($error($tag + 1)'TagValueIndent'$data);
  307.                     if ($fix === true{
  308.                         $phpcsFile->fixer->replaceToken(($tag + 1)str_repeat(' '$required));
  309.                     }
  310.                 }
  311.             }
  312.         }//end foreach
  313.  
  314.         // If there is a param group, it needs to be first.
  315.         if ($paramGroupid !== null && $paramGroupid !== 0{
  316.             $error 'Parameter tags must be defined first in a doc comment';
  317.             $phpcsFile->addError($error$tagGroups[$paramGroupid][0]'ParamNotFirst');
  318.         }
  319.  
  320.         $foundTags = array();
  321.         foreach ($tokens[$stackPtr]['comment_tags'as $pos => $tag{
  322.             $tagName $tokens[$tag]['content'];
  323.             if (isset($foundTags[$tagName]=== true{
  324.                 $lastTag $tokens[$stackPtr]['comment_tags'][($pos - 1)];
  325.                 if ($tokens[$lastTag]['content'!== $tagName{
  326.                     $error 'Tags must be grouped together in a doc comment';
  327.                     $phpcsFile->addError($error$tag'TagsNotGrouped');
  328.                 }
  329.  
  330.                 continue;
  331.             }
  332.  
  333.             $foundTags[$tagName= true;
  334.         }
  335.  
  336.     }//end process()
  337.  
  338.  
  339. }//end class

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