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

Source for file FunctionCallSignatureSniff.php

Documentation is available at FunctionCallSignatureSniff.php

  1. <?php
  2. /**
  3.  * Ensures function calls are formatted 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.  
  16. class FunctionCallSignatureSniff implements Sniff
  17. {
  18.  
  19.     /**
  20.      * A list of tokenizers this sniff supports.
  21.      *
  22.      * @var array 
  23.      */
  24.     public $supportedTokenizers = array(
  25.                                    'PHP',
  26.                                    'JS',
  27.                                   );
  28.  
  29.     /**
  30.      * The number of spaces code should be indented.
  31.      *
  32.      * @var integer 
  33.      */
  34.     public $indent = 4;
  35.  
  36.     /**
  37.      * If TRUE, multiple arguments can be defined per line in a multi-line call.
  38.      *
  39.      * @var boolean 
  40.      */
  41.     public $allowMultipleArguments = true;
  42.  
  43.     /**
  44.      * How many spaces should follow the opening bracket.
  45.      *
  46.      * @var integer 
  47.      */
  48.     public $requiredSpacesAfterOpen = 0;
  49.  
  50.     /**
  51.      * How many spaces should precede the closing bracket.
  52.      *
  53.      * @var integer 
  54.      */
  55.     public $requiredSpacesBeforeClose = 0;
  56.  
  57.  
  58.     /**
  59.      * Returns an array of tokens this test wants to listen for.
  60.      *
  61.      * @return array 
  62.      */
  63.     public function register()
  64.     {
  65.         $tokens = Tokens::$functionNameTokens;
  66.  
  67.         $tokens[= T_VARIABLE;
  68.         $tokens[T_CLOSE_CURLY_BRACKET;
  69.         $tokens[T_CLOSE_PARENTHESIS;
  70.  
  71.         return $tokens;
  72.  
  73.     }//end register()
  74.  
  75.  
  76.     /**
  77.      * Processes this test, when one of its tokens is encountered.
  78.      *
  79.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  80.      * @param int                         $stackPtr  The position of the current token
  81.      *                                                in the stack passed in $tokens.
  82.      *
  83.      * @return void 
  84.      */
  85.     public function process(File $phpcsFile$stackPtr)
  86.     {
  87.         $this->requiredSpacesAfterOpen   = (int) $this->requiredSpacesAfterOpen;
  88.         $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose;
  89.         $tokens $phpcsFile->getTokens();
  90.  
  91.         // Find the next non-empty token.
  92.         $openBracket $phpcsFile->findNext(Tokens::$emptyTokens($stackPtr + 1)nulltrue);
  93.  
  94.         if ($tokens[$openBracket]['code'!== T_OPEN_PARENTHESIS{
  95.             // Not a function call.
  96.             return;
  97.         }
  98.  
  99.         if (isset($tokens[$openBracket]['parenthesis_closer']=== false{
  100.             // Not a function call.
  101.             return;
  102.         }
  103.  
  104.         // Find the previous non-empty token.
  105.         $search   = Tokens::$emptyTokens;
  106.         $search[= T_BITWISE_AND;
  107.         $previous $phpcsFile->findPrevious($search($stackPtr - 1)nulltrue);
  108.         if ($tokens[$previous]['code'=== T_FUNCTION{
  109.             // It's a function definition, not a function call.
  110.             return;
  111.         }
  112.  
  113.         $closeBracket $tokens[$openBracket]['parenthesis_closer'];
  114.  
  115.         if (($stackPtr + 1!== $openBracket{
  116.             // Checking this: $value = my_function[*](...).
  117.             $error 'Space before opening parenthesis of function call prohibited';
  118.             $fix   $phpcsFile->addFixableError($error$stackPtr'SpaceBeforeOpenBracket');
  119.             if ($fix === true{
  120.                 $phpcsFile->fixer->beginChangeset();
  121.                 for ($i ($stackPtr + 1)$i $openBracket$i++{
  122.                     $phpcsFile->fixer->replaceToken($i'');
  123.                 }
  124.  
  125.                 // Modify the bracket as well to ensure a conflict if the bracket
  126.                 // has been changed in some way by another sniff.
  127.                 $phpcsFile->fixer->replaceToken($openBracket'(');
  128.                 $phpcsFile->fixer->endChangeset();
  129.             }
  130.         }
  131.  
  132.         $next $phpcsFile->findNext(T_WHITESPACE($closeBracket + 1)nulltrue);
  133.         if ($tokens[$next]['code'=== T_SEMICOLON{
  134.             if (isset(Tokens::$emptyTokens[$tokens[($closeBracket + 1)]['code']]=== true{
  135.                 $error 'Space after closing parenthesis of function call prohibited';
  136.                 $fix   $phpcsFile->addFixableError($error$closeBracket'SpaceAfterCloseBracket');
  137.                 if ($fix === true{
  138.                     $phpcsFile->fixer->beginChangeset();
  139.                     for ($i ($closeBracket + 1)$i $next$i++{
  140.                         $phpcsFile->fixer->replaceToken($i'');
  141.                     }
  142.  
  143.                     // Modify the bracket as well to ensure a conflict if the bracket
  144.                     // has been changed in some way by another sniff.
  145.                     $phpcsFile->fixer->replaceToken($closeBracket')');
  146.                     $phpcsFile->fixer->endChangeset();
  147.                 }
  148.             }
  149.         }
  150.  
  151.         // Check if this is a single line or multi-line function call.
  152.         if ($this->isMultiLineCall($phpcsFile$stackPtr$openBracket$tokens=== true{
  153.             $this->processMultiLineCall($phpcsFile$stackPtr$openBracket$tokens);
  154.         else {
  155.             $this->processSingleLineCall($phpcsFile$stackPtr$openBracket$tokens);
  156.         }
  157.  
  158.     }//end process()
  159.  
  160.  
  161.     /**
  162.      * Determine if this is a multi-line function call.
  163.      *
  164.      * @param \PHP_CodeSniffer\Files\File $phpcsFile   The file being scanned.
  165.      * @param int                         $stackPtr    The position of the current token
  166.      *                                                  in the stack passed in $tokens.
  167.      * @param int                         $openBracket The position of the opening bracket
  168.      *                                                  in the stack passed in $tokens.
  169.      * @param array                       $tokens      The stack of tokens that make up
  170.      *                                                  the file.
  171.      *
  172.      * @return void 
  173.      */
  174.     public function isMultiLineCall(File $phpcsFile$stackPtr$openBracket$tokens)
  175.     {
  176.         $closeBracket $tokens[$openBracket]['parenthesis_closer'];
  177.         if ($tokens[$openBracket]['line'!== $tokens[$closeBracket]['line']{
  178.             return true;
  179.         }
  180.  
  181.         return false;
  182.  
  183.     }//end isMultiLineCall()
  184.  
  185.  
  186.     /**
  187.      * Processes single-line calls.
  188.      *
  189.      * @param \PHP_CodeSniffer\Files\File $phpcsFile   The file being scanned.
  190.      * @param int                         $stackPtr    The position of the current token
  191.      *                                                  in the stack passed in $tokens.
  192.      * @param int                         $openBracket The position of the opening bracket
  193.      *                                                  in the stack passed in $tokens.
  194.      * @param array                       $tokens      The stack of tokens that make up
  195.      *                                                  the file.
  196.      *
  197.      * @return void 
  198.      */
  199.     public function processSingleLineCall(File $phpcsFile$stackPtr$openBracket$tokens)
  200.     {
  201.         $closer $tokens[$openBracket]['parenthesis_closer'];
  202.         if ($openBracket === ($closer - 1)) {
  203.             return;
  204.         }
  205.  
  206.         if ($this->requiredSpacesAfterOpen === 0 && $tokens[($openBracket + 1)]['code'=== T_WHITESPACE{
  207.             // Checking this: $value = my_function([*]...).
  208.             $error 'Space after opening parenthesis of function call prohibited';
  209.             $fix   $phpcsFile->addFixableError($error$stackPtr'SpaceAfterOpenBracket');
  210.             if ($fix === true{
  211.                 $phpcsFile->fixer->replaceToken(($openBracket + 1)'');
  212.             }
  213.         else if ($this->requiredSpacesAfterOpen > 0{
  214.             $spaceAfterOpen = 0;
  215.             if ($tokens[($openBracket + 1)]['code'=== T_WHITESPACE{
  216.                 $spaceAfterOpen strlen($tokens[($openBracket + 1)]['content']);
  217.             }
  218.  
  219.             if ($spaceAfterOpen !== $this->requiredSpacesAfterOpen{
  220.                 $error 'Expected %s spaces after opening bracket; %s found';
  221.                 $data  = array(
  222.                           $this->requiredSpacesAfterOpen,
  223.                           $spaceAfterOpen,
  224.                          );
  225.                 $fix   $phpcsFile->addFixableError($error$stackPtr'SpaceAfterOpenBracket'$data);
  226.                 if ($fix === true{
  227.                     $padding str_repeat(' '$this->requiredSpacesAfterOpen);
  228.                     if ($spaceAfterOpen === 0{
  229.                         $phpcsFile->fixer->addContent($openBracket$padding);
  230.                     else {
  231.                         $phpcsFile->fixer->replaceToken(($openBracket + 1)$padding);
  232.                     }
  233.                 }
  234.             }
  235.         }//end if
  236.  
  237.         // Checking this: $value = my_function(...[*]).
  238.         $spaceBeforeClose = 0;
  239.         $prev $phpcsFile->findPrevious(T_WHITESPACE($closer - 1)$openBrackettrue);
  240.         if ($tokens[$prev]['code'=== T_END_HEREDOC || $tokens[$prev]['code'=== T_END_NOWDOC{
  241.             // Need a newline after these tokens, so ignore this rule.
  242.             return;
  243.         }
  244.  
  245.         if ($tokens[$prev]['line'!== $tokens[$closer]['line']{
  246.             $spaceBeforeClose 'newline';
  247.         else if ($tokens[($closer - 1)]['code'=== T_WHITESPACE{
  248.             $spaceBeforeClose strlen($tokens[($closer - 1)]['content']);
  249.         }
  250.  
  251.         if ($spaceBeforeClose !== $this->requiredSpacesBeforeClose{
  252.             $error 'Expected %s spaces before closing bracket; %s found';
  253.             $data  = array(
  254.                       $this->requiredSpacesBeforeClose,
  255.                       $spaceBeforeClose,
  256.                      );
  257.             $fix   $phpcsFile->addFixableError($error$stackPtr'SpaceBeforeCloseBracket'$data);
  258.             if ($fix === true{
  259.                 $padding str_repeat(' '$this->requiredSpacesBeforeClose);
  260.  
  261.                 if ($spaceBeforeClose === 0{
  262.                     $phpcsFile->fixer->addContentBefore($closer$padding);
  263.                 else if ($spaceBeforeClose === 'newline'{
  264.                     $phpcsFile->fixer->beginChangeset();
  265.  
  266.                     $closingContent ')';
  267.  
  268.                     $next $phpcsFile->findNext(T_WHITESPACE($closer + 1)nulltrue);
  269.                     if ($tokens[$next]['code'=== T_SEMICOLON{
  270.                         $closingContent .= ';';
  271.                         for ($i ($closer + 1)$i <= $next$i++{
  272.                             $phpcsFile->fixer->replaceToken($i'');
  273.                         }
  274.                     }
  275.  
  276.                     // We want to jump over any whitespace or inline comment and
  277.                     // move the closing parenthesis after any other token.
  278.                     $prev ($closer - 1);
  279.                     while (isset(Tokens::$emptyTokens[$tokens[$prev]['code']]=== true{
  280.                         if (($tokens[$prev]['code'=== T_COMMENT)
  281.                             && (strpos($tokens[$prev]['content']'*/'!== false)
  282.                         {
  283.                             break;
  284.                         }
  285.  
  286.                         $prev--;
  287.                     }
  288.  
  289.                     $phpcsFile->fixer->addContent($prev$padding.$closingContent);
  290.  
  291.                     $prevNonWhitespace $phpcsFile->findPrevious(T_WHITESPACE($closer - 1)nulltrue);
  292.                     for ($i ($prevNonWhitespace + 1)$i <= $closer$i++{
  293.                         $phpcsFile->fixer->replaceToken($i'');
  294.                     }
  295.  
  296.                     $phpcsFile->fixer->endChangeset();
  297.                 else {
  298.                     $phpcsFile->fixer->replaceToken(($closer - 1)$padding);
  299.                 }//end if
  300.             }//end if
  301.         }//end if
  302.  
  303.     }//end processSingleLineCall()
  304.  
  305.  
  306.     /**
  307.      * Processes multi-line calls.
  308.      *
  309.      * @param \PHP_CodeSniffer\Files\File $phpcsFile   The file being scanned.
  310.      * @param int                         $stackPtr    The position of the current token
  311.      *                                                  in the stack passed in $tokens.
  312.      * @param int                         $openBracket The position of the opening bracket
  313.      *                                                  in the stack passed in $tokens.
  314.      * @param array                       $tokens      The stack of tokens that make up
  315.      *                                                  the file.
  316.      *
  317.      * @return void 
  318.      */
  319.     public function processMultiLineCall(File $phpcsFile$stackPtr$openBracket$tokens)
  320.     {
  321.         // We need to work out how far indented the function
  322.         // call itself is, so we can work out how far to
  323.         // indent the arguments.
  324.         $start $phpcsFile->findStartOfStatement($stackPtr);
  325.         foreach (array('stackPtr''start'as $checkToken{
  326.             $x = $$checkToken;
  327.             for ($i ($x - 1)$i >= 0; $i--{
  328.                 if ($tokens[$i]['line'!== $tokens[$x]['line']{
  329.                     $i++;
  330.                     break;
  331.                 }
  332.             }
  333.  
  334.             if ($i <= 0{
  335.                 $functionIndent = 0;
  336.             else if ($tokens[$i]['code'=== T_WHITESPACE{
  337.                 $functionIndent strlen($tokens[$i]['content']);
  338.             else if ($tokens[$i]['code'=== T_CONSTANT_ENCAPSED_STRING{
  339.                 $functionIndent = 0;
  340.             else {
  341.                 $trimmed ltrim($tokens[$i]['content']);
  342.                 if ($trimmed === ''{
  343.                     if ($tokens[$i]['code'=== T_INLINE_HTML{
  344.                         $functionIndent strlen($tokens[$i]['content']);
  345.                     else {
  346.                         $functionIndent ($tokens[$i]['column'- 1);
  347.                     }
  348.                 else {
  349.                     $functionIndent (strlen($tokens[$i]['content']strlen($trimmed));
  350.                 }
  351.             }
  352.  
  353.             $varName  $checkToken.'Indent';
  354.             $$varName $functionIndent;
  355.         }//end foreach
  356.  
  357.         $functionIndent max($startIndent$stackPtrIndent);
  358.  
  359.         $next $phpcsFile->findNext(Tokens::$emptyTokens($openBracket + 1)nulltrue);
  360.         if ($tokens[$next]['line'=== $tokens[$openBracket]['line']{
  361.             $error 'Opening parenthesis of a multi-line function call must be the last content on the line';
  362.             $fix   $phpcsFile->addFixableError($error$stackPtr'ContentAfterOpenBracket');
  363.             if ($fix === true{
  364.                 $phpcsFile->fixer->addContent(
  365.                     $openBracket,
  366.                     $phpcsFile->eolChar.str_repeat(' '($functionIndent $this->indent))
  367.                 );
  368.             }
  369.         }
  370.  
  371.         $closeBracket $tokens[$openBracket]['parenthesis_closer'];
  372.         $prev         $phpcsFile->findPrevious(T_WHITESPACE($closeBracket - 1)nulltrue);
  373.         if ($tokens[$prev]['line'=== $tokens[$closeBracket]['line']{
  374.             $error 'Closing parenthesis of a multi-line function call must be on a line by itself';
  375.             $fix   $phpcsFile->addFixableError($error$closeBracket'CloseBracketLine');
  376.             if ($fix === true{
  377.                 $phpcsFile->fixer->addContentBefore(
  378.                     $closeBracket,
  379.                     $phpcsFile->eolChar.str_repeat(' '($functionIndent $this->indent))
  380.                 );
  381.             }
  382.         }
  383.  
  384.         // Each line between the parenthesis should be indented n spaces.
  385.         $lastLine ($tokens[$openBracket]['line'- 1);
  386.         $argStart = null;
  387.         $argEnd   = null;
  388.         $inArg    = false;
  389.  
  390.         // Start processing at the first argument.
  391.         $i $phpcsFile->findNext(T_WHITESPACE($openBracket + 1)nulltrue);
  392.         if ($tokens[($i - 1)]['code'=== T_WHITESPACE
  393.             && $tokens[($i - 1)]['line'=== $tokens[$i]['line']
  394.         {
  395.             // Make sure we check the indent.
  396.             $i--;
  397.         }
  398.  
  399.         for ($i$i $closeBracket$i++{
  400.             if ($i $argStart && $i $argEnd{
  401.                 $inArg = true;
  402.             else {
  403.                 $inArg = false;
  404.             }
  405.  
  406.             if ($tokens[$i]['line'!== $lastLine{
  407.                 $lastLine $tokens[$i]['line'];
  408.  
  409.                 // Ignore heredoc indentation.
  410.                 if (isset(Tokens::$heredocTokens[$tokens[$i]['code']]=== true{
  411.                     continue;
  412.                 }
  413.  
  414.                 // Ignore multi-line string indentation.
  415.                 if (isset(Tokens::$stringTokens[$tokens[$i]['code']]=== true
  416.                     && $tokens[$i]['code'=== $tokens[($i - 1)]['code']
  417.                 {
  418.                     continue;
  419.                 }
  420.  
  421.                 // Ignore inline HTML.
  422.                 if ($tokens[$i]['code'=== T_INLINE_HTML{
  423.                     continue;
  424.                 }
  425.  
  426.                 if ($tokens[$i]['line'!== $tokens[$openBracket]['line']{
  427.                     // We changed lines, so this should be a whitespace indent token, but first make
  428.                     // sure it isn't a blank line because we don't need to check indent unless there
  429.                     // is actually some code to indent.
  430.                     if ($tokens[$i]['code'=== T_WHITESPACE{
  431.                         $nextCode $phpcsFile->findNext(T_WHITESPACE($i + 1)($closeBracket + 1)true);
  432.                         if ($tokens[$nextCode]['line'!== $lastLine{
  433.                             if ($inArg === false{
  434.                                 $error 'Empty lines are not allowed in multi-line function calls';
  435.                                 $fix   $phpcsFile->addFixableError($error$i'EmptyLine');
  436.                                 if ($fix === true{
  437.                                     $phpcsFile->fixer->replaceToken($i'');
  438.                                 }
  439.                             }
  440.  
  441.                             continue;
  442.                         }
  443.                     else {
  444.                         $nextCode $i;
  445.                     }
  446.  
  447.                     if ($tokens[$nextCode]['line'=== $tokens[$closeBracket]['line']{
  448.                         // Closing brace needs to be indented to the same level
  449.                         // as the function call.
  450.                         $inArg          = false;
  451.                         $expectedIndent $functionIndent;
  452.                     else {
  453.                         $expectedIndent ($functionIndent $this->indent);
  454.                     }
  455.  
  456.                     if ($tokens[$i]['code'!== T_WHITESPACE
  457.                         && $tokens[$i]['code'!== T_DOC_COMMENT_WHITESPACE
  458.                     {
  459.                         // Just check if it is a multi-line block comment. If so, we can
  460.                         // calculate the indent from the whitespace before the content.
  461.                         if ($tokens[$i]['code'=== T_COMMENT
  462.                             && $tokens[($i - 1)]['code'=== T_COMMENT
  463.                         {
  464.                             $trimmedLength strlen(ltrim($tokens[$i]['content']));
  465.                             if ($trimmedLength === 0{
  466.                                 // This is a blank comment line, so indenting it is
  467.                                 // pointless.
  468.                                 continue;
  469.                             }
  470.  
  471.                             $foundIndent (strlen($tokens[$i]['content']$trimmedLength);
  472.                         else {
  473.                             $foundIndent = 0;
  474.                         }
  475.                     else {
  476.                         $foundIndent strlen($tokens[$i]['content']);
  477.                     }
  478.  
  479.                     if ($foundIndent $expectedIndent
  480.                         || ($inArg === false
  481.                         && $expectedIndent !== $foundIndent)
  482.                     {
  483.                         $error 'Multi-line function call not indented correctly; expected %s spaces but found %s';
  484.                         $data  = array(
  485.                                   $expectedIndent,
  486.                                   $foundIndent,
  487.                                  );
  488.  
  489.                         $fix $phpcsFile->addFixableError($error$i'Indent'$data);
  490.                         if ($fix === true{
  491.                             $padding str_repeat(' '$expectedIndent);
  492.                             if ($foundIndent === 0{
  493.                                 $phpcsFile->fixer->addContentBefore($i$padding);
  494.                             else {
  495.                                 if ($tokens[$i]['code'=== T_COMMENT{
  496.                                     $comment $padding.ltrim($tokens[$i]['content']);
  497.                                     $phpcsFile->fixer->replaceToken($i$comment);
  498.                                 else {
  499.                                     $phpcsFile->fixer->replaceToken($i$padding);
  500.                                 }
  501.                             }
  502.                         }
  503.                     }//end if
  504.                 else {
  505.                     $nextCode $i;
  506.                 }//end if
  507.  
  508.                 if ($inArg === false{
  509.                     $argStart $nextCode;
  510.                     $argEnd   $phpcsFile->findEndOfStatement($nextCode);
  511.                 }
  512.             }//end if
  513.  
  514.             // If we are within an argument we should be ignoring commas
  515.             // as these are not signaling the end of an argument.
  516.             if ($inArg === false && $tokens[$i]['code'=== T_COMMA{
  517.                 $next $phpcsFile->findNext(array(T_WHITESPACET_COMMENT)($i + 1)$closeBrackettrue);
  518.                 if ($next === false{
  519.                     continue;
  520.                 }
  521.  
  522.                 if ($this->allowMultipleArguments === false{
  523.                     // Comma has to be the last token on the line.
  524.                     if ($tokens[$i]['line'=== $tokens[$next]['line']{
  525.                         $error 'Only one argument is allowed per line in a multi-line function call';
  526.                         $fix   $phpcsFile->addFixableError($error$next'MultipleArguments');
  527.                         if ($fix === true{
  528.                             $phpcsFile->fixer->beginChangeset();
  529.                             for ($x ($next - 1)$x $i$x--{
  530.                                 if ($tokens[$x]['code'!== T_WHITESPACE{
  531.                                     break;
  532.                                 }
  533.  
  534.                                 $phpcsFile->fixer->replaceToken($x'');
  535.                             }
  536.  
  537.                             $phpcsFile->fixer->addContentBefore(
  538.                                 $next,
  539.                                 $phpcsFile->eolChar.str_repeat(' '($functionIndent $this->indent))
  540.                             );
  541.                             $phpcsFile->fixer->endChangeset();
  542.                         }
  543.                     }
  544.                 }//end if
  545.  
  546.                 $argStart $next;
  547.                 $argEnd   $phpcsFile->findEndOfStatement($next);
  548.             }//end if
  549.         }//end for
  550.  
  551.     }//end processMultiLineCall()
  552.  
  553.  
  554. }//end class

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