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

Source for file FunctionCommentSniff.php

Documentation is available at FunctionCommentSniff.php

  1. <?php
  2. /**
  3.  * Parses and verifies the doc comments for functions.
  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\PEAR\Sniffs\Commenting;
  11.  
  12. use PHP_CodeSniffer\Sniffs\Sniff;
  13. use PHP_CodeSniffer\Files\File;
  14. use PHP_CodeSniffer\Util\Tokens;
  15.  
  16. class FunctionCommentSniff implements Sniff
  17. {
  18.  
  19.  
  20.     /**
  21.      * Returns an array of tokens this test wants to listen for.
  22.      *
  23.      * @return array 
  24.      */
  25.     public function register()
  26.     {
  27.         return array(T_FUNCTION);
  28.  
  29.     }//end register()
  30.  
  31.  
  32.     /**
  33.      * Processes this test, when one of its tokens is encountered.
  34.      *
  35.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  36.      * @param int                         $stackPtr  The position of the current token
  37.      *                                                in the stack passed in $tokens.
  38.      *
  39.      * @return void 
  40.      */
  41.     public function process(File $phpcsFile$stackPtr)
  42.     {
  43.         $tokens $phpcsFile->getTokens();
  44.         $find   = Tokens::$methodPrefixes;
  45.         $find[= T_WHITESPACE;
  46.  
  47.         $commentEnd $phpcsFile->findPrevious($find($stackPtr - 1)nulltrue);
  48.         if ($tokens[$commentEnd]['code'=== T_COMMENT{
  49.             // Inline comments might just be closing comments for
  50.             // control structures or functions instead of function comments
  51.             // using the wrong comment type. If there is other code on the line,
  52.             // assume they relate to that code.
  53.             $prev $phpcsFile->findPrevious($find($commentEnd - 1)nulltrue);
  54.             if ($prev !== false && $tokens[$prev]['line'=== $tokens[$commentEnd]['line']{
  55.                 $commentEnd $prev;
  56.             }
  57.         }
  58.  
  59.         if ($tokens[$commentEnd]['code'!== T_DOC_COMMENT_CLOSE_TAG
  60.             && $tokens[$commentEnd]['code'!== T_COMMENT
  61.         {
  62.             $phpcsFile->addError('Missing function doc comment'$stackPtr'Missing');
  63.             $phpcsFile->recordMetric($stackPtr'Function has doc comment''no');
  64.             return;
  65.         else {
  66.             $phpcsFile->recordMetric($stackPtr'Function has doc comment''yes');
  67.         }
  68.  
  69.         if ($tokens[$commentEnd]['code'=== T_COMMENT{
  70.             $phpcsFile->addError('You must use "/**" style comments for a function comment'$stackPtr'WrongStyle');
  71.             return;
  72.         }
  73.  
  74.         if ($tokens[$commentEnd]['line'!== ($tokens[$stackPtr]['line'- 1)) {
  75.             $error 'There must be no blank lines after the function comment';
  76.             $phpcsFile->addError($error$commentEnd'SpacingAfter');
  77.         }
  78.  
  79.         $commentStart $tokens[$commentEnd]['comment_opener'];
  80.         foreach ($tokens[$commentStart]['comment_tags'as $tag{
  81.             if ($tokens[$tag]['content'=== '@see'{
  82.                 // Make sure the tag isn't empty.
  83.                 $string $phpcsFile->findNext(T_DOC_COMMENT_STRING$tag$commentEnd);
  84.                 if ($string === false || $tokens[$string]['line'!== $tokens[$tag]['line']{
  85.                     $error 'Content missing for @see tag in function comment';
  86.                     $phpcsFile->addError($error$tag'EmptySees');
  87.                 }
  88.             }
  89.         }
  90.  
  91.         $this->processReturn($phpcsFile$stackPtr$commentStart);
  92.         $this->processThrows($phpcsFile$stackPtr$commentStart);
  93.         $this->processParams($phpcsFile$stackPtr$commentStart);
  94.  
  95.     }//end process()
  96.  
  97.  
  98.     /**
  99.      * Process the return comment of this function comment.
  100.      *
  101.      * @param \PHP_CodeSniffer\Files\File $phpcsFile    The file being scanned.
  102.      * @param int                         $stackPtr     The position of the current token
  103.      *                                                   in the stack passed in $tokens.
  104.      * @param int                         $commentStart The position in the stack where the comment started.
  105.      *
  106.      * @return void 
  107.      */
  108.     protected function processReturn(File $phpcsFile$stackPtr$commentStart)
  109.     {
  110.         $tokens $phpcsFile->getTokens();
  111.  
  112.         // Skip constructor and destructor.
  113.         $methodName      $phpcsFile->getDeclarationName($stackPtr);
  114.         $isSpecialMethod ($methodName === '__construct' || $methodName === '__destruct');
  115.  
  116.         $return = null;
  117.         foreach ($tokens[$commentStart]['comment_tags'as $tag{
  118.             if ($tokens[$tag]['content'=== '@return'{
  119.                 if ($return !== null{
  120.                     $error 'Only 1 @return tag is allowed in a function comment';
  121.                     $phpcsFile->addError($error$tag'DuplicateReturn');
  122.                     return;
  123.                 }
  124.  
  125.                 $return $tag;
  126.             }
  127.         }
  128.  
  129.         if ($isSpecialMethod === true{
  130.             return;
  131.         }
  132.  
  133.         if ($return !== null{
  134.             $content $tokens[($return + 2)]['content'];
  135.             if (empty($content=== true || $tokens[($return + 2)]['code'!== T_DOC_COMMENT_STRING{
  136.                 $error 'Return type missing for @return tag in function comment';
  137.                 $phpcsFile->addError($error$return'MissingReturnType');
  138.             }
  139.         else {
  140.             $error 'Missing @return tag in function comment';
  141.             $phpcsFile->addError($error$tokens[$commentStart]['comment_closer']'MissingReturn');
  142.         }//end if
  143.  
  144.     }//end processReturn()
  145.  
  146.  
  147.     /**
  148.      * Process any throw tags that this function comment has.
  149.      *
  150.      * @param \PHP_CodeSniffer\Files\File $phpcsFile    The file being scanned.
  151.      * @param int                         $stackPtr     The position of the current token
  152.      *                                                   in the stack passed in $tokens.
  153.      * @param int                         $commentStart The position in the stack where the comment started.
  154.      *
  155.      * @return void 
  156.      */
  157.     protected function processThrows(File $phpcsFile$stackPtr$commentStart)
  158.     {
  159.         $tokens $phpcsFile->getTokens();
  160.  
  161.         $throws = array();
  162.         foreach ($tokens[$commentStart]['comment_tags'as $tag{
  163.             if ($tokens[$tag]['content'!== '@throws'{
  164.                 continue;
  165.             }
  166.  
  167.             $exception = null;
  168.             $comment   = null;
  169.             if ($tokens[($tag + 2)]['code'=== T_DOC_COMMENT_STRING{
  170.                 $matches = array();
  171.                 preg_match('/([^\s]+)(?:\s+(.*))?/'$tokens[($tag + 2)]['content']$matches);
  172.                 $exception $matches[1];
  173.                 if (isset($matches[2]=== true{
  174.                     $comment $matches[2];
  175.                 }
  176.             }
  177.  
  178.             if ($exception === null{
  179.                 $error 'Exception type missing for @throws tag in function comment';
  180.                 $phpcsFile->addError($error$tag'InvalidThrows');
  181.             }
  182.         }//end foreach
  183.  
  184.     }//end processThrows()
  185.  
  186.  
  187.     /**
  188.      * Process the function parameter comments.
  189.      *
  190.      * @param \PHP_CodeSniffer\Files\File $phpcsFile    The file being scanned.
  191.      * @param int                         $stackPtr     The position of the current token
  192.      *                                                   in the stack passed in $tokens.
  193.      * @param int                         $commentStart The position in the stack where the comment started.
  194.      *
  195.      * @return void 
  196.      */
  197.     protected function processParams(File $phpcsFile$stackPtr$commentStart)
  198.     {
  199.         $tokens $phpcsFile->getTokens();
  200.  
  201.         $params  = array();
  202.         $maxType = 0;
  203.         $maxVar  = 0;
  204.         foreach ($tokens[$commentStart]['comment_tags'as $pos => $tag{
  205.             if ($tokens[$tag]['content'!== '@param'{
  206.                 continue;
  207.             }
  208.  
  209.             $type          '';
  210.             $typeSpace     = 0;
  211.             $var           '';
  212.             $varSpace      = 0;
  213.             $comment       '';
  214.             $commentEnd    = 0;
  215.             $commentTokens = array();
  216.  
  217.             if ($tokens[($tag + 2)]['code'=== T_DOC_COMMENT_STRING{
  218.                 $matches = array();
  219.                 preg_match('/([^$&.]+)(?:((?:\.\.\.)?(?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/'$tokens[($tag + 2)]['content']$matches);
  220.  
  221.                 if (empty($matches=== false{
  222.                     $typeLen   strlen($matches[1]);
  223.                     $type      trim($matches[1]);
  224.                     $typeSpace ($typeLen strlen($type));
  225.                     $typeLen   strlen($type);
  226.                     if ($typeLen $maxType{
  227.                         $maxType $typeLen;
  228.                     }
  229.                 }
  230.  
  231.                 if (isset($matches[2]=== true{
  232.                     $var    $matches[2];
  233.                     $varLen strlen($var);
  234.                     if ($varLen $maxVar{
  235.                         $maxVar $varLen;
  236.                     }
  237.  
  238.                     if (isset($matches[4]=== true{
  239.                         $varSpace strlen($matches[3]);
  240.                         $comment  $matches[4];
  241.  
  242.                         // Any strings until the next tag belong to this comment.
  243.                         if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]=== true{
  244.                             $end $tokens[$commentStart]['comment_tags'][($pos + 1)];
  245.                         else {
  246.                             $end $tokens[$commentStart]['comment_closer'];
  247.                         }
  248.  
  249.                         for ($i ($tag + 3)$i $end$i++{
  250.                             if ($tokens[$i]['code'=== T_DOC_COMMENT_STRING{
  251.                                 $comment        .= ' '.$tokens[$i]['content'];
  252.                                 $commentEnd      $i;
  253.                                 $commentTokens[$i;
  254.                             }
  255.                         }
  256.                     else {
  257.                         $error 'Missing parameter comment';
  258.                         $phpcsFile->addError($error$tag'MissingParamComment');
  259.                     }//end if
  260.                 else {
  261.                     $error 'Missing parameter name';
  262.                     $phpcsFile->addError($error$tag'MissingParamName');
  263.                 }//end if
  264.             else {
  265.                 $error 'Missing parameter type';
  266.                 $phpcsFile->addError($error$tag'MissingParamType');
  267.             }//end if
  268.  
  269.             $params[= array(
  270.                          'tag'            => $tag,
  271.                          'type'           => $type,
  272.                          'var'            => $var,
  273.                          'comment'        => $comment,
  274.                          'comment_end'    => $commentEnd,
  275.                          'comment_tokens' => $commentTokens,
  276.                          'type_space'     => $typeSpace,
  277.                          'var_space'      => $varSpace,
  278.                         );
  279.         }//end foreach
  280.  
  281.         $realParams  $phpcsFile->getMethodParameters($stackPtr);
  282.         $foundParams = array();
  283.  
  284.         // We want to use ... for all variable length arguments, so add
  285.         // this prefix to the variable name so comparisons are easier.
  286.         foreach ($realParams as $pos => $param{
  287.             if ($param['variable_length'=== true{
  288.                 $realParams[$pos]['name''...'.$realParams[$pos]['name'];
  289.             }
  290.         }
  291.  
  292.         foreach ($params as $pos => $param{
  293.             if ($param['var'=== ''{
  294.                 continue;
  295.             }
  296.  
  297.             $foundParams[$param['var'];
  298.  
  299.             // Check number of spaces after the type.
  300.             $spaces ($maxType strlen($param['type']+ 1);
  301.             if ($param['type_space'!== $spaces{
  302.                 $error 'Expected %s spaces after parameter type; %s found';
  303.                 $data  = array(
  304.                           $spaces,
  305.                           $param['type_space'],
  306.                          );
  307.  
  308.                 $fix $phpcsFile->addFixableError($error$param['tag']'SpacingAfterParamType'$data);
  309.                 if ($fix === true{
  310.                     $commentToken ($param['tag'+ 2);
  311.  
  312.                     $content  $param['type'];
  313.                     $content .= str_repeat(' '$spaces);
  314.                     $content .= $param['var'];
  315.                     $content .= str_repeat(' '$param['var_space']);
  316.  
  317.                     $wrapLength ($tokens[$commentToken]['length'$param['type_space'$param['var_space'strlen($param['type']strlen($param['var']));
  318.  
  319.                     $star        $phpcsFile->findPrevious(T_DOC_COMMENT_STAR$param['tag']);
  320.                     $spaceLength (strlen($content$tokens[($commentToken - 1)]['length'$tokens[($commentToken - 2)]['length']);
  321.  
  322.                     $padding  str_repeat(' '($tokens[$star]['column'- 1));
  323.                     $padding .= '* ';
  324.                     $padding .= str_repeat(' '$spaceLength);
  325.  
  326.                     $content .= wordwrap(
  327.                         $param['comment'],
  328.                         $wrapLength,
  329.                         $phpcsFile->eolChar.$padding
  330.                     );
  331.  
  332.                     $phpcsFile->fixer->replaceToken($commentToken$content);
  333.                     for ($i ($commentToken + 1)$i <= $param['comment_end']$i++{
  334.                         $phpcsFile->fixer->replaceToken($i'');
  335.                     }
  336.                 }//end if
  337.             }//end if
  338.  
  339.             // Make sure the param name is correct.
  340.             if (isset($realParams[$pos]=== true{
  341.                 $realName $realParams[$pos]['name'];
  342.                 if ($realName !== $param['var']{
  343.                     $code 'ParamNameNoMatch';
  344.                     $data = array(
  345.                              $param['var'],
  346.                              $realName,
  347.                             );
  348.  
  349.                     $error 'Doc comment for parameter %s does not match ';
  350.                     if (strtolower($param['var']=== strtolower($realName)) {
  351.                         $error .= 'case of ';
  352.                         $code   'ParamNameNoCaseMatch';
  353.                     }
  354.  
  355.                     $error .= 'actual variable name %s';
  356.  
  357.                     $phpcsFile->addError($error$param['tag']$code$data);
  358.                 }
  359.             else if (substr($param['var']-4!== ',...'{
  360.                 // We must have an extra parameter comment.
  361.                 $error 'Superfluous parameter comment';
  362.                 $phpcsFile->addError($error$param['tag']'ExtraParamComment');
  363.             }//end if
  364.  
  365.             if ($param['comment'=== ''{
  366.                 continue;
  367.             }
  368.  
  369.             // Check number of spaces after the param name.
  370.             $spaces ($maxVar strlen($param['var']+ 1);
  371.             if ($param['var_space'!== $spaces{
  372.                 $error 'Expected %s spaces after parameter name; %s found';
  373.                 $data  = array(
  374.                           $spaces,
  375.                           $param['var_space'],
  376.                          );
  377.  
  378.                 $fix $phpcsFile->addFixableError($error$param['tag']'SpacingAfterParamName'$data);
  379.                 if ($fix === true{
  380.                     $commentToken ($param['tag'+ 2);
  381.  
  382.                     $content  $param['type'];
  383.                     $content .= str_repeat(' '$param['type_space']);
  384.                     $content .= $param['var'];
  385.                     $content .= str_repeat(' '$spaces);
  386.  
  387.                     $wrapLength ($tokens[$commentToken]['length'$param['type_space'$param['var_space'strlen($param['type']strlen($param['var']));
  388.  
  389.                     $star        $phpcsFile->findPrevious(T_DOC_COMMENT_STAR$param['tag']);
  390.                     $spaceLength (strlen($content$tokens[($commentToken - 1)]['length'$tokens[($commentToken - 2)]['length']);
  391.  
  392.                     $padding  str_repeat(' '($tokens[$star]['column'- 1));
  393.                     $padding .= '* ';
  394.                     $padding .= str_repeat(' '$spaceLength);
  395.  
  396.                     $content .= wordwrap(
  397.                         $param['comment'],
  398.                         $wrapLength,
  399.                         $phpcsFile->eolChar.$padding
  400.                     );
  401.  
  402.                     $phpcsFile->fixer->replaceToken($commentToken$content);
  403.                     for ($i ($commentToken + 1)$i <= $param['comment_end']$i++{
  404.                         $phpcsFile->fixer->replaceToken($i'');
  405.                     }
  406.                 }//end if
  407.             }//end if
  408.  
  409.             // Check the alignment of multi-line param comments.
  410.             if ($param['tag'!== $param['comment_end']{
  411.                 $wrapLength ($tokens[($param['tag'+ 2)]['length'$param['type_space'$param['var_space'strlen($param['type']strlen($param['var']));
  412.  
  413.                 $startColumn ($tokens[($param['tag'+ 2)]['column'$tokens[($param['tag'+ 2)]['length'$wrapLength);
  414.  
  415.                 $star     $phpcsFile->findPrevious(T_DOC_COMMENT_STAR$param['tag']);
  416.                 $expected ($startColumn $tokens[$star]['column'- 1);
  417.  
  418.                 foreach ($param['comment_tokens'as $commentToken{
  419.                     if ($tokens[$commentToken]['column'=== $startColumn{
  420.                         continue;
  421.                     }
  422.  
  423.                     $found = 0;
  424.                     if ($tokens[($commentToken - 1)]['code'=== T_DOC_COMMENT_WHITESPACE{
  425.                         $found $tokens[($commentToken - 1)]['length'];
  426.                     }
  427.  
  428.                     $error 'Parameter comment not aligned correctly; expected %s spaces but found %s';
  429.                     $data  = array(
  430.                               $expected,
  431.                               $found,
  432.                              );
  433.                     $fix   $phpcsFile->addFixableError($error$commentToken'ParamCommentAlignment'$data);
  434.                     if ($fix === true{
  435.                         $padding str_repeat(' '$expected);
  436.                         if ($tokens[($commentToken - 1)]['code'=== T_DOC_COMMENT_WHITESPACE{
  437.                             $phpcsFile->fixer->replaceToken(($commentToken - 1)$padding);
  438.                         else {
  439.                             $phpcsFile->fixer->addContentBefore($commentToken$padding);
  440.                         }
  441.                     }
  442.                 }//end foreach
  443.             }//end if
  444.         }//end foreach
  445.  
  446.         $realNames = array();
  447.         foreach ($realParams as $realParam{
  448.             $realNames[$realParam['name'];
  449.         }
  450.  
  451.         // Report missing comments.
  452.         $diff array_diff($realNames$foundParams);
  453.         foreach ($diff as $neededParam{
  454.             $error 'Doc comment for parameter "%s" missing';
  455.             $data  = array($neededParam);
  456.             $phpcsFile->addError($error$commentStart'MissingParamTag'$data);
  457.         }
  458.  
  459.     }//end processParams()
  460.  
  461.  
  462. }//end class

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