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_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_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_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(Tokens::$emptyTokens($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.                 if ($spaceBeforeClose === 0{
  255.                     $phpcsFile->fixer->addContentBefore($closer$padding);
  256.                 else {
  257.                     $phpcsFile->fixer->replaceToken(($closer - 1)$padding);
  258.                 }
  259.             }
  260.         }
  261.  
  262.     }//end processSingleLineCall()
  263.  
  264.  
  265.     /**
  266.      * Processes multi-line calls.
  267.      *
  268.      * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
  269.      * @param int                  $stackPtr    The position of the current token
  270.      *                                           in the stack passed in $tokens.
  271.      * @param int                  $openBracket The position of the opening bracket
  272.      *                                           in the stack passed in $tokens.
  273.      * @param array                $tokens      The stack of tokens that make up
  274.      *                                           the file.
  275.      *
  276.      * @return void 
  277.      */
  278.     public function processMultiLineCall(File $phpcsFile$stackPtr$openBracket$tokens)
  279.     {
  280.         // We need to work out how far indented the function
  281.         // call itself is, so we can work out how far to
  282.         // indent the arguments.
  283.         $start $phpcsFile->findStartOfStatement($stackPtr);
  284.         foreach (array('stackPtr''start'as $checkToken{
  285.             $x = $$checkToken;
  286.             for ($i ($x - 1)$i >= 0; $i--{
  287.                 if ($tokens[$i]['line'!== $tokens[$x]['line']{
  288.                     $i++;
  289.                     break;
  290.                 }
  291.             }
  292.  
  293.             if ($i <= 0{
  294.                 $functionIndent = 0;
  295.             else if ($tokens[$i]['code'=== T_WHITESPACE{
  296.                 $functionIndent strlen($tokens[$i]['content']);
  297.             else if ($tokens[$i]['code'=== T_CONSTANT_ENCAPSED_STRING{
  298.                 $functionIndent = 0;
  299.             else {
  300.                 $trimmed ltrim($tokens[$i]['content']);
  301.                 if ($trimmed === ''{
  302.                     if ($tokens[$i]['code'=== T_INLINE_HTML{
  303.                         $functionIndent strlen($tokens[$i]['content']);
  304.                     else {
  305.                         $functionIndent ($tokens[$i]['column'- 1);
  306.                     }
  307.                 else {
  308.                     $functionIndent (strlen($tokens[$i]['content']strlen($trimmed));
  309.                 }
  310.             }
  311.  
  312.             $varName  $checkToken.'Indent';
  313.             $$varName $functionIndent;
  314.         }//end foreach
  315.  
  316.         $functionIndent max($startIndent$stackPtrIndent);
  317.  
  318.         $next $phpcsFile->findNext(Tokens::$emptyTokens($openBracket + 1)nulltrue);
  319.         if ($tokens[$next]['line'=== $tokens[$openBracket]['line']{
  320.             $error 'Opening parenthesis of a multi-line function call must be the last content on the line';
  321.             $fix   $phpcsFile->addFixableError($error$stackPtr'ContentAfterOpenBracket');
  322.             if ($fix === true{
  323.                 $phpcsFile->fixer->addContent(
  324.                     $openBracket,
  325.                     $phpcsFile->eolChar.str_repeat(' '($functionIndent $this->indent))
  326.                 );
  327.             }
  328.         }
  329.  
  330.         $closeBracket $tokens[$openBracket]['parenthesis_closer'];
  331.         $prev         $phpcsFile->findPrevious(T_WHITESPACE($closeBracket - 1)nulltrue);
  332.         if ($tokens[$prev]['line'=== $tokens[$closeBracket]['line']{
  333.             $error 'Closing parenthesis of a multi-line function call must be on a line by itself';
  334.             $fix   $phpcsFile->addFixableError($error$closeBracket'CloseBracketLine');
  335.             if ($fix === true{
  336.                 $phpcsFile->fixer->addContentBefore(
  337.                     $closeBracket,
  338.                     $phpcsFile->eolChar.str_repeat(' '($functionIndent $this->indent))
  339.                 );
  340.             }
  341.         }
  342.  
  343.         // Each line between the parenthesis should be indented n spaces.
  344.         $lastLine ($tokens[$openBracket]['line'- 1);
  345.         $argStart = null;
  346.         $argEnd   = null;
  347.         $inArg    = false;
  348.  
  349.         // Start processing at the first argument.
  350.         $i $phpcsFile->findNext(T_WHITESPACE($openBracket + 1)nulltrue);
  351.         if ($tokens[($i - 1)]['code'=== T_WHITESPACE
  352.             && $tokens[($i - 1)]['line'=== $tokens[$i]['line']
  353.         {
  354.             // Make sure we check the indent.
  355.             $i--;
  356.         }
  357.  
  358.         for ($i$i $closeBracket$i++{
  359.             if ($i $argStart && $i $argEnd{
  360.                 $inArg = true;
  361.             else {
  362.                 $inArg = false;
  363.             }
  364.  
  365.             if ($tokens[$i]['line'!== $lastLine{
  366.                 $lastLine $tokens[$i]['line'];
  367.  
  368.                 // Ignore heredoc indentation.
  369.                 if (isset(Tokens::$heredocTokens[$tokens[$i]['code']]=== true{
  370.                     continue;
  371.                 }
  372.  
  373.                 // Ignore multi-line string indentation.
  374.                 if (isset(Tokens::$stringTokens[$tokens[$i]['code']]=== true
  375.                     && $tokens[$i]['code'=== $tokens[($i - 1)]['code']
  376.                 {
  377.                     continue;
  378.                 }
  379.  
  380.                 // Ignore inline HTML.
  381.                 if ($tokens[$i]['code'=== T_INLINE_HTML{
  382.                     continue;
  383.                 }
  384.  
  385.                 if ($tokens[$i]['line'!== $tokens[$openBracket]['line']{
  386.                     // We changed lines, so this should be a whitespace indent token, but first make
  387.                     // sure it isn't a blank line because we don't need to check indent unless there
  388.                     // is actually some code to indent.
  389.                     if ($tokens[$i]['code'=== T_WHITESPACE{
  390.                         $nextCode $phpcsFile->findNext(T_WHITESPACE($i + 1)($closeBracket + 1)true);
  391.                         if ($tokens[$nextCode]['line'!== $lastLine{
  392.                             if ($inArg === false{
  393.                                 $error 'Empty lines are not allowed in multi-line function calls';
  394.                                 $fix   $phpcsFile->addFixableError($error$i'EmptyLine');
  395.                                 if ($fix === true{
  396.                                     $phpcsFile->fixer->replaceToken($i'');
  397.                                 }
  398.                             }
  399.  
  400.                             continue;
  401.                         }
  402.                     else {
  403.                         $nextCode $i;
  404.                     }
  405.  
  406.                     if ($tokens[$nextCode]['line'=== $tokens[$closeBracket]['line']{
  407.                         // Closing brace needs to be indented to the same level
  408.                         // as the function call.
  409.                         $inArg          = false;
  410.                         $expectedIndent $functionIndent;
  411.                     else {
  412.                         $expectedIndent ($functionIndent $this->indent);
  413.                     }
  414.  
  415.                     if ($tokens[$i]['code'!== T_WHITESPACE
  416.                         && $tokens[$i]['code'!== T_DOC_COMMENT_WHITESPACE
  417.                     {
  418.                         // Just check if it is a multi-line block comment. If so, we can
  419.                         // calculate the indent from the whitespace before the content.
  420.                         if ($tokens[$i]['code'=== T_COMMENT
  421.                             && $tokens[($i - 1)]['code'=== T_COMMENT
  422.                         {
  423.                             $trimmed     ltrim($tokens[$i]['content']);
  424.                             $foundIndent (strlen($tokens[$i]['content']strlen($trimmed));
  425.                         else {
  426.                             $foundIndent = 0;
  427.                         }
  428.                     else {
  429.                         $foundIndent strlen($tokens[$i]['content']);
  430.                     }
  431.  
  432.                     if ($foundIndent $expectedIndent
  433.                         || ($inArg === false
  434.                         && $expectedIndent !== $foundIndent)
  435.                     {
  436.                         $error 'Multi-line function call not indented correctly; expected %s spaces but found %s';
  437.                         $data  = array(
  438.                                   $expectedIndent,
  439.                                   $foundIndent,
  440.                                  );
  441.  
  442.                         $fix $phpcsFile->addFixableError($error$i'Indent'$data);
  443.                         if ($fix === true{
  444.                             $padding str_repeat(' '$expectedIndent);
  445.                             if ($foundIndent === 0{
  446.                                 $phpcsFile->fixer->addContentBefore($i$padding);
  447.                             else {
  448.                                 if ($tokens[$i]['code'=== T_COMMENT{
  449.                                     $comment $padding.ltrim($tokens[$i]['content']);
  450.                                     $phpcsFile->fixer->replaceToken($i$comment);
  451.                                 else {
  452.                                     $phpcsFile->fixer->replaceToken($i$padding);
  453.                                 }
  454.                             }
  455.                         }
  456.                     }//end if
  457.                 else {
  458.                     $nextCode $i;
  459.                 }//end if
  460.  
  461.                 if ($inArg === false{
  462.                     $argStart $nextCode;
  463.                     $argEnd   $phpcsFile->findEndOfStatement($nextCode);
  464.                 }
  465.             }//end if
  466.  
  467.             // If we are within an argument we should be ignoring commas
  468.             // as these are not signaling the end of an argument.
  469.             if ($inArg === false && $tokens[$i]['code'=== T_COMMA{
  470.                 $next $phpcsFile->findNext(array(T_WHITESPACET_COMMENT)($i + 1)$closeBrackettrue);
  471.                 if ($next === false{
  472.                     continue;
  473.                 }
  474.  
  475.                 if ($this->allowMultipleArguments === false{
  476.                     // Comma has to be the last token on the line.
  477.                     if ($tokens[$i]['line'=== $tokens[$next]['line']{
  478.                         $error 'Only one argument is allowed per line in a multi-line function call';
  479.                         $fix   $phpcsFile->addFixableError($error$next'MultipleArguments');
  480.                         if ($fix === true{
  481.                             $phpcsFile->fixer->beginChangeset();
  482.                             for ($x ($next - 1)$x $i$x--{
  483.                                 if ($tokens[$x]['code'!== T_WHITESPACE{
  484.                                     break;
  485.                                 }
  486.  
  487.                                 $phpcsFile->fixer->replaceToken($x'');
  488.                             }
  489.  
  490.                             $phpcsFile->fixer->addContentBefore(
  491.                                 $next,
  492.                                 $phpcsFile->eolChar.str_repeat(' '($functionIndent $this->indent))
  493.                             );
  494.                             $phpcsFile->fixer->endChangeset();
  495.                         }
  496.                     }
  497.                 }//end if
  498.  
  499.                 $argStart $next;
  500.                 $argEnd   $phpcsFile->findEndOfStatement($next);
  501.             }//end if
  502.         }//end for
  503.  
  504.     }//end processMultiLineCall()
  505.  
  506.  
  507. }//end class

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