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

Source for file FunctionDeclarationSniff.php

Documentation is available at FunctionDeclarationSniff.php

  1. <?php
  2. /**
  3.  * Ensure single and multi-line function declarations are defined correctly.
  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\Functions;
  11.  
  12. use PHP_CodeSniffer\Sniffs\Sniff;
  13. use PHP_CodeSniffer\Files\File;
  14. use PHP_CodeSniffer\Util\Tokens;
  15. use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceKernighanRitchieSniff;
  16. use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceBsdAllmanSniff;
  17.  
  18. class FunctionDeclarationSniff implements Sniff
  19. {
  20.  
  21.     /**
  22.      * A list of tokenizers this sniff supports.
  23.      *
  24.      * @var array 
  25.      */
  26.     public $supportedTokenizers = array(
  27.                                    'PHP',
  28.                                    'JS',
  29.                                   );
  30.  
  31.     /**
  32.      * The number of spaces code should be indented.
  33.      *
  34.      * @var integer 
  35.      */
  36.     public $indent = 4;
  37.  
  38.  
  39.     /**
  40.      * Returns an array of tokens this test wants to listen for.
  41.      *
  42.      * @return array 
  43.      */
  44.     public function register()
  45.     {
  46.         return array(
  47.                 T_FUNCTION,
  48.                 T_CLOSURE,
  49.                );
  50.  
  51.     }//end register()
  52.  
  53.  
  54.     /**
  55.      * Processes this test, when one of its tokens is encountered.
  56.      *
  57.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  58.      * @param int                         $stackPtr  The position of the current token
  59.      *                                                in the stack passed in $tokens.
  60.      *
  61.      * @return void 
  62.      */
  63.     public function process(File $phpcsFile$stackPtr)
  64.     {
  65.         $tokens $phpcsFile->getTokens();
  66.  
  67.         if (isset($tokens[$stackPtr]['parenthesis_opener']=== false
  68.             || isset($tokens[$stackPtr]['parenthesis_closer']=== false
  69.             || $tokens[$stackPtr]['parenthesis_opener'=== null
  70.             || $tokens[$stackPtr]['parenthesis_closer'=== null
  71.         {
  72.             return;
  73.         }
  74.  
  75.         $openBracket  $tokens[$stackPtr]['parenthesis_opener'];
  76.         $closeBracket $tokens[$stackPtr]['parenthesis_closer'];
  77.  
  78.         if (strtolower($tokens[$stackPtr]['content']=== 'function'{
  79.             // Must be one space after the FUNCTION keyword.
  80.             if ($tokens[($stackPtr + 1)]['content'=== $phpcsFile->eolChar{
  81.                 $spaces 'newline';
  82.             else if ($tokens[($stackPtr + 1)]['code'=== T_WHITESPACE{
  83.                 $spaces strlen($tokens[($stackPtr + 1)]['content']);
  84.             else {
  85.                 $spaces = 0;
  86.             }
  87.  
  88.             if ($spaces !== 1{
  89.                 $error 'Expected 1 space after FUNCTION keyword; %s found';
  90.                 $data  = array($spaces);
  91.                 $fix   $phpcsFile->addFixableError($error$stackPtr'SpaceAfterFunction'$data);
  92.                 if ($fix === true{
  93.                     if ($spaces === 0{
  94.                         $phpcsFile->fixer->addContent($stackPtr' ');
  95.                     else {
  96.                         $phpcsFile->fixer->replaceToken(($stackPtr + 1)' ');
  97.                     }
  98.                 }
  99.             }
  100.         }//end if
  101.  
  102.         // Must be one space before the opening parenthesis. For closures, this is
  103.         // enforced by the first check because there is no content between the keywords
  104.         // and the opening parenthesis.
  105.         if ($tokens[$stackPtr]['code'=== T_FUNCTION{
  106.             if ($tokens[($openBracket - 1)]['content'=== $phpcsFile->eolChar{
  107.                 $spaces 'newline';
  108.             else if ($tokens[($openBracket - 1)]['code'=== T_WHITESPACE{
  109.                 $spaces strlen($tokens[($openBracket - 1)]['content']);
  110.             else {
  111.                 $spaces = 0;
  112.             }
  113.  
  114.             if ($spaces !== 0{
  115.                 $error 'Expected 0 spaces before opening parenthesis; %s found';
  116.                 $data  = array($spaces);
  117.                 $fix   $phpcsFile->addFixableError($error$openBracket'SpaceBeforeOpenParen'$data);
  118.                 if ($fix === true{
  119.                     $phpcsFile->fixer->replaceToken(($openBracket - 1)'');
  120.                 }
  121.             }
  122.         }//end if
  123.  
  124.         // Must be one space before and after USE keyword for closures.
  125.         if ($tokens[$stackPtr]['code'=== T_CLOSURE{
  126.             $use $phpcsFile->findNext(T_USE($closeBracket + 1)$tokens[$stackPtr]['scope_opener']);
  127.             if ($use !== false{
  128.                 if ($tokens[($use + 1)]['code'!== T_WHITESPACE{
  129.                     $length = 0;
  130.                 else if ($tokens[($use + 1)]['content'=== "\t"{
  131.                     $length '\t';
  132.                 else {
  133.                     $length strlen($tokens[($use + 1)]['content']);
  134.                 }
  135.  
  136.                 if ($length !== 1{
  137.                     $error 'Expected 1 space after USE keyword; found %s';
  138.                     $data  = array($length);
  139.                     $fix   $phpcsFile->addFixableError($error$use'SpaceAfterUse'$data);
  140.                     if ($fix === true{
  141.                         if ($length === 0{
  142.                             $phpcsFile->fixer->addContent($use' ');
  143.                         else {
  144.                             $phpcsFile->fixer->replaceToken(($use + 1)' ');
  145.                         }
  146.                     }
  147.                 }
  148.  
  149.                 if ($tokens[($use - 1)]['code'!== T_WHITESPACE{
  150.                     $length = 0;
  151.                 else if ($tokens[($use - 1)]['content'=== "\t"{
  152.                     $length '\t';
  153.                 else {
  154.                     $length strlen($tokens[($use - 1)]['content']);
  155.                 }
  156.  
  157.                 if ($length !== 1{
  158.                     $error 'Expected 1 space before USE keyword; found %s';
  159.                     $data  = array($length);
  160.                     $fix   $phpcsFile->addFixableError($error$use'SpaceBeforeUse'$data);
  161.                     if ($fix === true{
  162.                         if ($length === 0{
  163.                             $phpcsFile->fixer->addContentBefore($use' ');
  164.                         else {
  165.                             $phpcsFile->fixer->replaceToken(($use - 1)' ');
  166.                         }
  167.                     }
  168.                 }
  169.             }//end if
  170.         }//end if
  171.  
  172.         if ($this->isMultiLineDeclaration($phpcsFile$stackPtr$openBracket$tokens=== true{
  173.             $this->processMultiLineDeclaration($phpcsFile$stackPtr$tokens);
  174.         else {
  175.             $this->processSingleLineDeclaration($phpcsFile$stackPtr$tokens);
  176.         }
  177.  
  178.     }//end process()
  179.  
  180.  
  181.     /**
  182.      * Determine if this is a multi-line function declaration.
  183.      *
  184.      * @param \PHP_CodeSniffer\Files\File $phpcsFile   The file being scanned.
  185.      * @param int                         $stackPtr    The position of the current token
  186.      *                                                  in the stack passed in $tokens.
  187.      * @param int                         $openBracket The position of the opening bracket
  188.      *                                                  in the stack passed in $tokens.
  189.      * @param array                       $tokens      The stack of tokens that make up
  190.      *                                                  the file.
  191.      *
  192.      * @return void 
  193.      */
  194.     public function isMultiLineDeclaration($phpcsFile$stackPtr$openBracket$tokens)
  195.     {
  196.         $closeBracket $tokens[$openBracket]['parenthesis_closer'];
  197.         if ($tokens[$openBracket]['line'!== $tokens[$closeBracket]['line']{
  198.             return true;
  199.         }
  200.  
  201.         // Closures may use the USE keyword and so be multi-line in this way.
  202.         if ($tokens[$stackPtr]['code'=== T_CLOSURE{
  203.             $use $phpcsFile->findNext(T_USE($closeBracket + 1)$tokens[$stackPtr]['scope_opener']);
  204.             if ($use !== false{
  205.                 // If the opening and closing parenthesis of the use statement
  206.                 // are also on the same line, this is a single line declaration.
  207.                 $open  $phpcsFile->findNext(T_OPEN_PARENTHESIS($use + 1));
  208.                 $close $tokens[$open]['parenthesis_closer'];
  209.                 if ($tokens[$open]['line'!== $tokens[$close]['line']{
  210.                     return true;
  211.                 }
  212.             }
  213.         }
  214.  
  215.         return false;
  216.  
  217.     }//end isMultiLineDeclaration()
  218.  
  219.  
  220.     /**
  221.      * Processes single-line declarations.
  222.      *
  223.      * Just uses the Generic BSD-Allman brace sniff.
  224.      *
  225.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  226.      * @param int                         $stackPtr  The position of the current token
  227.      *                                                in the stack passed in $tokens.
  228.      * @param array                       $tokens    The stack of tokens that make up
  229.      *                                                the file.
  230.      *
  231.      * @return void 
  232.      */
  233.     public function processSingleLineDeclaration($phpcsFile$stackPtr$tokens)
  234.     {
  235.         if ($tokens[$stackPtr]['code'=== T_CLOSURE{
  236.             $sniff = new OpeningFunctionBraceKernighanRitchieSniff();
  237.         else {
  238.             $sniff = new OpeningFunctionBraceBsdAllmanSniff();
  239.         }
  240.  
  241.         $sniff->checkClosures = true;
  242.         $sniff->process($phpcsFile$stackPtr);
  243.  
  244.     }//end processSingleLineDeclaration()
  245.  
  246.  
  247.     /**
  248.      * Processes multi-line declarations.
  249.      *
  250.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  251.      * @param int                         $stackPtr  The position of the current token
  252.      *                                                in the stack passed in $tokens.
  253.      * @param array                       $tokens    The stack of tokens that make up
  254.      *                                                the file.
  255.      *
  256.      * @return void 
  257.      */
  258.     public function processMultiLineDeclaration($phpcsFile$stackPtr$tokens)
  259.     {
  260.         // We need to work out how far indented the function
  261.         // declaration itself is, so we can work out how far to
  262.         // indent parameters.
  263.         $functionIndent = 0;
  264.         for ($i ($stackPtr - 1)$i >= 0; $i--{
  265.             if ($tokens[$i]['line'!== $tokens[$stackPtr]['line']{
  266.                 $i++;
  267.                 break;
  268.             }
  269.         }
  270.  
  271.         if ($tokens[$i]['code'=== T_WHITESPACE{
  272.             $functionIndent strlen($tokens[$i]['content']);
  273.         }
  274.  
  275.         // The closing parenthesis must be on a new line, even
  276.         // when checking abstract function definitions.
  277.         $closeBracket $tokens[$stackPtr]['parenthesis_closer'];
  278.         $prev         $phpcsFile->findPrevious(
  279.             T_WHITESPACE,
  280.             ($closeBracket - 1),
  281.             null,
  282.             true
  283.         );
  284.  
  285.         if ($tokens[$closeBracket]['line'!== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']{
  286.             if ($tokens[$prev]['line'=== $tokens[$closeBracket]['line']{
  287.                 $error 'The closing parenthesis of a multi-line function declaration must be on a new line';
  288.                 $fix   $phpcsFile->addFixableError($error$closeBracket'CloseBracketLine');
  289.                 if ($fix === true{
  290.                     $phpcsFile->fixer->addNewlineBefore($closeBracket);
  291.                 }
  292.             }
  293.         }
  294.  
  295.         // If this is a closure and is using a USE statement, the closing
  296.         // parenthesis we need to look at from now on is the closing parenthesis
  297.         // of the USE statement.
  298.         if ($tokens[$stackPtr]['code'=== T_CLOSURE{
  299.             $use $phpcsFile->findNext(T_USE($closeBracket + 1)$tokens[$stackPtr]['scope_opener']);
  300.             if ($use !== false{
  301.                 $open         $phpcsFile->findNext(T_OPEN_PARENTHESIS($use + 1));
  302.                 $closeBracket $tokens[$open]['parenthesis_closer'];
  303.  
  304.                 $prev $phpcsFile->findPrevious(
  305.                     T_WHITESPACE,
  306.                     ($closeBracket - 1),
  307.                     null,
  308.                     true
  309.                 );
  310.  
  311.                 if ($tokens[$closeBracket]['line'!== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']{
  312.                     if ($tokens[$prev]['line'=== $tokens[$closeBracket]['line']{
  313.                         $error 'The closing parenthesis of a multi-line use declaration must be on a new line';
  314.                         $fix   $phpcsFile->addFixableError($error$closeBracket'UseCloseBracketLine');
  315.                         if ($fix === true{
  316.                             $phpcsFile->fixer->addNewlineBefore($closeBracket);
  317.                         }
  318.                     }
  319.                 }
  320.             }//end if
  321.         }//end if
  322.  
  323.         // Each line between the parenthesis should be indented 4 spaces.
  324.         $openBracket $tokens[$stackPtr]['parenthesis_opener'];
  325.         $lastLine    $tokens[$openBracket]['line'];
  326.         for ($i ($openBracket + 1)$i $closeBracket$i++{
  327.             if ($tokens[$i]['line'!== $lastLine{
  328.                 if ($i === $tokens[$stackPtr]['parenthesis_closer']
  329.                     || ($tokens[$i]['code'=== T_WHITESPACE
  330.                     && (($i + 1=== $closeBracket
  331.                     || ($i + 1=== $tokens[$stackPtr]['parenthesis_closer']))
  332.                 {
  333.                     // Closing braces need to be indented to the same level
  334.                     // as the function.
  335.                     $expectedIndent $functionIndent;
  336.                 else {
  337.                     $expectedIndent ($functionIndent $this->indent);
  338.                 }
  339.  
  340.                 // We changed lines, so this should be a whitespace indent token.
  341.                 if ($tokens[$i]['code'!== T_WHITESPACE{
  342.                     $foundIndent = 0;
  343.                 else if ($tokens[$i]['line'!== $tokens[($i + 1)]['line']{
  344.                     // This is an empty line, so don't check the indent.
  345.                     $foundIndent $expectedIndent;
  346.  
  347.                     $error 'Blank lines are not allowed in a multi-line function declaration';
  348.                     $fix   $phpcsFile->addFixableError($error$i'EmptyLine');
  349.                     if ($fix === true{
  350.                         $phpcsFile->fixer->replaceToken($i'');
  351.                     }
  352.                 else {
  353.                     $foundIndent strlen($tokens[$i]['content']);
  354.                 }
  355.  
  356.                 if ($expectedIndent !== $foundIndent{
  357.                     $error 'Multi-line function declaration not indented correctly; expected %s spaces but found %s';
  358.                     $data  = array(
  359.                               $expectedIndent,
  360.                               $foundIndent,
  361.                              );
  362.  
  363.                     $fix $phpcsFile->addFixableError($error$i'Indent'$data);
  364.                     if ($fix === true{
  365.                         $spaces str_repeat(' '$expectedIndent);
  366.                         if ($foundIndent === 0{
  367.                             $phpcsFile->fixer->addContentBefore($i$spaces);
  368.                         else {
  369.                             $phpcsFile->fixer->replaceToken($i$spaces);
  370.                         }
  371.                     }
  372.                 }
  373.  
  374.                 $lastLine $tokens[$i]['line'];
  375.             }//end if
  376.  
  377.             if ($tokens[$i]['code'=== T_ARRAY || $tokens[$i]['code'=== T_OPEN_SHORT_ARRAY{
  378.                 // Skip arrays as they have their own indentation rules.
  379.                 if ($tokens[$i]['code'=== T_OPEN_SHORT_ARRAY{
  380.                     $i $tokens[$i]['bracket_closer'];
  381.                 else {
  382.                     $i $tokens[$i]['parenthesis_closer'];
  383.                 }
  384.  
  385.                 $lastLine $tokens[$i]['line'];
  386.                 continue;
  387.             }
  388.         }//end for
  389.  
  390.         if (isset($tokens[$stackPtr]['scope_opener']=== false{
  391.             return;
  392.         }
  393.  
  394.         // The opening brace needs to be one space away from the closing parenthesis.
  395.         $opener $tokens[$stackPtr]['scope_opener'];
  396.         if ($tokens[$opener]['line'!== $tokens[$closeBracket]['line']{
  397.             $error 'The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line';
  398.             $fix   $phpcsFile->addFixableError($error$opener'NewlineBeforeOpenBrace');
  399.             if ($fix === true{
  400.                 $prev $phpcsFile->findPrevious(Tokens::$emptyTokens($opener - 1)$closeBrackettrue);
  401.                 $phpcsFile->fixer->beginChangeset();
  402.                 $phpcsFile->fixer->addContent($prev' {');
  403.  
  404.                 // If the opener is on a line by itself, removing it will create
  405.                 // an empty line, so just remove the entire line instead.
  406.                 $prev $phpcsFile->findPrevious(T_WHITESPACE($opener - 1)$closeBrackettrue);
  407.                 $next $phpcsFile->findNext(T_WHITESPACE($opener + 1)nulltrue);
  408.  
  409.                 if ($tokens[$prev]['line'$tokens[$opener]['line']
  410.                     && $tokens[$next]['line'$tokens[$opener]['line']
  411.                 {
  412.                     // Clear the whole line.
  413.                     for ($i ($prev + 1)$i $next$i++{
  414.                         if ($tokens[$i]['line'=== $tokens[$opener]['line']{
  415.                             $phpcsFile->fixer->replaceToken($i'');
  416.                         }
  417.                     }
  418.                 else {
  419.                     // Just remove the opener.
  420.                     $phpcsFile->fixer->replaceToken($opener'');
  421.                     if ($tokens[$next]['line'=== $tokens[$opener]['line']{
  422.                         $phpcsFile->fixer->replaceToken(($opener + 1)'');
  423.                     }
  424.                 }
  425.  
  426.                 $phpcsFile->fixer->endChangeset();
  427.             }//end if
  428.         else {
  429.             $prev $tokens[($opener - 1)];
  430.             if ($prev['code'!== T_WHITESPACE{
  431.                 $length = 0;
  432.             else {
  433.                 $length strlen($prev['content']);
  434.             }
  435.  
  436.             if ($length !== 1{
  437.                 $error 'There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration; found %s spaces';
  438.                 $fix   $phpcsFile->addFixableError($error($opener - 1)'SpaceBeforeOpenBrace'array($length));
  439.                 if ($fix === true{
  440.                     if ($length === 0{
  441.                         $phpcsFile->fixer->addContentBefore($opener' ');
  442.                     else {
  443.                         $phpcsFile->fixer->replaceToken(($opener - 1)' ');
  444.                     }
  445.                 }
  446.  
  447.                 return;
  448.             }//end if
  449.         }//end if
  450.  
  451.     }//end processMultiLineDeclaration()
  452.  
  453.  
  454. }//end class

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