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

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