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

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