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_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_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_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_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.             if ($tokens[($tag + 2)]['code'=== T_DOC_COMMENT_STRING{
  215.                 $matches = array();
  216.                 preg_match('/([^$&.]+)(?:((?:\.\.\.)?(?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/'$tokens[($tag + 2)]['content']$matches);
  217.  
  218.                 if (empty($matches=== false{
  219.                     $typeLen   strlen($matches[1]);
  220.                     $type      trim($matches[1]);
  221.                     $typeSpace ($typeLen strlen($type));
  222.                     $typeLen   strlen($type);
  223.                     if ($typeLen $maxType{
  224.                         $maxType $typeLen;
  225.                     }
  226.                 }
  227.  
  228.                 if (isset($matches[2]=== true{
  229.                     $var    $matches[2];
  230.                     $varLen strlen($var);
  231.                     if ($varLen $maxVar{
  232.                         $maxVar $varLen;
  233.                     }
  234.  
  235.                     if (isset($matches[4]=== true{
  236.                         $varSpace strlen($matches[3]);
  237.                         $comment  $matches[4];
  238.  
  239.                         // Any strings until the next tag belong to this comment.
  240.                         if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]=== true{
  241.                             $end $tokens[$commentStart]['comment_tags'][($pos + 1)];
  242.                         else {
  243.                             $end $tokens[$commentStart]['comment_closer'];
  244.                         }
  245.  
  246.                         for ($i ($tag + 3)$i $end$i++{
  247.                             if ($tokens[$i]['code'=== T_DOC_COMMENT_STRING{
  248.                                 $comment .= ' '.$tokens[$i]['content'];
  249.                             }
  250.                         }
  251.                     else {
  252.                         $error 'Missing parameter comment';
  253.                         $phpcsFile->addError($error$tag'MissingParamComment');
  254.                     }
  255.                 else {
  256.                     $error 'Missing parameter name';
  257.                     $phpcsFile->addError($error$tag'MissingParamName');
  258.                 }//end if
  259.             else {
  260.                 $error 'Missing parameter type';
  261.                 $phpcsFile->addError($error$tag'MissingParamType');
  262.             }//end if
  263.  
  264.             $params[= array(
  265.                          'tag'        => $tag,
  266.                          'type'       => $type,
  267.                          'var'        => $var,
  268.                          'comment'    => $comment,
  269.                          'type_space' => $typeSpace,
  270.                          'var_space'  => $varSpace,
  271.                         );
  272.         }//end foreach
  273.  
  274.         $realParams  $phpcsFile->getMethodParameters($stackPtr);
  275.         $foundParams = array();
  276.  
  277.         // We want to use ... for all variable length arguments, so added
  278.         // this prefix to the variable name so comparisons are easier.
  279.         foreach ($realParams as $pos => $param{
  280.             if ($param['variable_length'=== true{
  281.                 $realParams[$pos]['name''...'.$realParams[$pos]['name'];
  282.             }
  283.         }
  284.  
  285.         foreach ($params as $pos => $param{
  286.             if ($param['var'=== ''{
  287.                 continue;
  288.             }
  289.  
  290.             $foundParams[$param['var'];
  291.  
  292.             // Check number of spaces after the type.
  293.             $spaces ($maxType strlen($param['type']+ 1);
  294.             if ($param['type_space'!== $spaces{
  295.                 $error 'Expected %s spaces after parameter type; %s found';
  296.                 $data  = array(
  297.                           $spaces,
  298.                           $param['type_space'],
  299.                          );
  300.  
  301.                 $fix $phpcsFile->addFixableError($error$param['tag']'SpacingAfterParamType'$data);
  302.                 if ($fix === true{
  303.                     $content  $param['type'];
  304.                     $content .= str_repeat(' '$spaces);
  305.                     $content .= $param['var'];
  306.                     $content .= str_repeat(' '$param['var_space']);
  307.                     $content .= $param['comment'];
  308.                     $phpcsFile->fixer->replaceToken(($param['tag'+ 2)$content);
  309.                 }
  310.             }
  311.  
  312.             // Make sure the param name is correct.
  313.             if (isset($realParams[$pos]=== true{
  314.                 $realName $realParams[$pos]['name'];
  315.                 if ($realName !== $param['var']{
  316.                     $code 'ParamNameNoMatch';
  317.                     $data = array(
  318.                              $param['var'],
  319.                              $realName,
  320.                             );
  321.  
  322.                     $error 'Doc comment for parameter %s does not match ';
  323.                     if (strtolower($param['var']=== strtolower($realName)) {
  324.                         $error .= 'case of ';
  325.                         $code   'ParamNameNoCaseMatch';
  326.                     }
  327.  
  328.                     $error .= 'actual variable name %s';
  329.  
  330.                     $phpcsFile->addError($error$param['tag']$code$data);
  331.                 }
  332.             else if (substr($param['var']-4!== ',...'{
  333.                 // We must have an extra parameter comment.
  334.                 $error 'Superfluous parameter comment';
  335.                 $phpcsFile->addError($error$param['tag']'ExtraParamComment');
  336.             }//end if
  337.  
  338.             if ($param['comment'=== ''{
  339.                 continue;
  340.             }
  341.  
  342.             // Check number of spaces after the var name.
  343.             $spaces ($maxVar strlen($param['var']+ 1);
  344.             if ($param['var_space'!== $spaces{
  345.                 $error 'Expected %s spaces after parameter name; %s found';
  346.                 $data  = array(
  347.                           $spaces,
  348.                           $param['var_space'],
  349.                          );
  350.  
  351.                 $fix $phpcsFile->addFixableError($error$param['tag']'SpacingAfterParamName'$data);
  352.                 if ($fix === true{
  353.                     $content  $param['type'];
  354.                     $content .= str_repeat(' '$param['type_space']);
  355.                     $content .= $param['var'];
  356.                     $content .= str_repeat(' '$spaces);
  357.                     $content .= $param['comment'];
  358.                     $phpcsFile->fixer->replaceToken(($param['tag'+ 2)$content);
  359.                 }
  360.             }
  361.         }//end foreach
  362.  
  363.         $realNames = array();
  364.         foreach ($realParams as $realParam{
  365.             $realNames[$realParam['name'];
  366.         }
  367.  
  368.         // Report missing comments.
  369.         $diff array_diff($realNames$foundParams);
  370.         foreach ($diff as $neededParam{
  371.             $error 'Doc comment for parameter "%s" missing';
  372.             $data  = array($neededParam);
  373.             $phpcsFile->addError($error$commentStart'MissingParamTag'$data);
  374.         }
  375.  
  376.     }//end processParams()
  377.  
  378.  
  379. }//end class

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