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

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