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

Source for file OperatorBracketSniff.php

Documentation is available at OperatorBracketSniff.php

  1. <?php
  2. /**
  3.  * Tests that all arithmetic operations are bracketed.
  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\Squiz\Sniffs\Formatting;
  11.  
  12. use PHP_CodeSniffer\Sniffs\Sniff;
  13. use PHP_CodeSniffer\Files\File;
  14. use PHP_CodeSniffer\Util\Tokens;
  15.  
  16. class OperatorBracketSniff 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.     /**
  31.      * Returns an array of tokens this test wants to listen for.
  32.      *
  33.      * @return array 
  34.      */
  35.     public function register()
  36.     {
  37.         return Tokens::$operators;
  38.  
  39.     }//end register()
  40.  
  41.  
  42.     /**
  43.      * Processes this test, when one of its tokens is encountered.
  44.      *
  45.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  46.      * @param int                         $stackPtr  The position of the current token in the
  47.      *                                                stack passed in $tokens.
  48.      *
  49.      * @return void 
  50.      */
  51.     public function process(File $phpcsFile$stackPtr)
  52.     {
  53.         $tokens $phpcsFile->getTokens();
  54.  
  55.         if ($phpcsFile->tokenizerType === 'JS' && $tokens[$stackPtr]['code'=== T_PLUS{
  56.             // JavaScript uses the plus operator for string concatenation as well
  57.             // so we cannot accurately determine if it is a string concat or addition.
  58.             // So just ignore it.
  59.             return;
  60.         }
  61.  
  62.         // If the & is a reference, then we don't want to check for brackets.
  63.         if ($tokens[$stackPtr]['code'=== T_BITWISE_AND && $phpcsFile->isReference($stackPtr=== true{
  64.             return;
  65.         }
  66.  
  67.         // There is one instance where brackets aren't needed, which involves
  68.         // the minus sign being used to assign a negative number to a variable.
  69.         if ($tokens[$stackPtr]['code'=== T_MINUS{
  70.             // Check to see if we are trying to return -n.
  71.             $prev $phpcsFile->findPrevious(Tokens::$emptyTokens($stackPtr - 1)nulltrue);
  72.             if ($tokens[$prev]['code'=== T_RETURN{
  73.                 return;
  74.             }
  75.  
  76.             $number $phpcsFile->findNext(T_WHITESPACE($stackPtr + 1)nulltrue);
  77.             if ($tokens[$number]['code'=== T_LNUMBER || $tokens[$number]['code'=== T_DNUMBER{
  78.                 $previous $phpcsFile->findPrevious(T_WHITESPACE($stackPtr - 1)nulltrue);
  79.                 if ($previous !== false{
  80.                     $isAssignment in_array($tokens[$previous]['code']Tokens::$assignmentTokens);
  81.                     $isEquality   = in_array($tokens[$previous]['code']Tokens::$equalityTokens);
  82.                     $isComparison = in_array($tokens[$previous]['code']Tokens::$comparisonTokens);
  83.                     if ($isAssignment === true || $isEquality === true || $isComparison === true{
  84.                         // This is a negative assignment or comparison.
  85.                         // We need to check that the minus and the number are
  86.                         // adjacent.
  87.                         if (($number $stackPtr!== 1{
  88.                             $error 'No space allowed between minus sign and number';
  89.                             $phpcsFile->addError($error$stackPtr'SpacingAfterMinus');
  90.                         }
  91.  
  92.                         return;
  93.                     }
  94.                 }
  95.             }
  96.         }//end if
  97.  
  98.         $previousToken $phpcsFile->findPrevious(T_WHITESPACE($stackPtr - 1)nulltruenulltrue);
  99.         if ($previousToken !== false{
  100.             // A list of tokens that indicate that the token is not
  101.             // part of an arithmetic operation.
  102.             $invalidTokens = array(
  103.                               T_COMMA,
  104.                               T_COLON,
  105.                               T_OPEN_PARENTHESIS,
  106.                               T_OPEN_SQUARE_BRACKET,
  107.                               T_OPEN_SHORT_ARRAY,
  108.                               T_CASE,
  109.                              );
  110.  
  111.             if (in_array($tokens[$previousToken]['code']$invalidTokens=== true{
  112.                 return;
  113.             }
  114.         }
  115.  
  116.         if ($tokens[$stackPtr]['code'=== T_BITWISE_OR
  117.             && isset($tokens[$stackPtr]['nested_parenthesis']=== true
  118.         {
  119.             $brackets    $tokens[$stackPtr]['nested_parenthesis'];
  120.             $lastBracket array_pop($brackets);
  121.             if (isset($tokens[$lastBracket]['parenthesis_owner']=== true
  122.                 && $tokens[$tokens[$lastBracket]['parenthesis_owner']]['code'=== T_CATCH
  123.             {
  124.                 // This is a pipe character inside a catch statement, so it is acting
  125.                 // as an exception type seperator and not an arithmetic operation.
  126.                 return;
  127.             }
  128.         }
  129.  
  130.         // Tokens that are allowed inside a bracketed operation.
  131.         $allowed = array(
  132.                     T_VARIABLE,
  133.                     T_LNUMBER,
  134.                     T_DNUMBER,
  135.                     T_STRING,
  136.                     T_WHITESPACE,
  137.                     T_NS_SEPARATOR,
  138.                     T_THIS,
  139.                     T_SELF,
  140.                     T_OBJECT_OPERATOR,
  141.                     T_DOUBLE_COLON,
  142.                     T_OPEN_SQUARE_BRACKET,
  143.                     T_CLOSE_SQUARE_BRACKET,
  144.                     T_MODULUS,
  145.                     T_NONE,
  146.                    );
  147.  
  148.         $allowed += Tokens::$operators;
  149.  
  150.         $lastBracket = false;
  151.         if (isset($tokens[$stackPtr]['nested_parenthesis']=== true{
  152.             $parenthesis array_reverse($tokens[$stackPtr]['nested_parenthesis']true);
  153.             foreach ($parenthesis as $bracket => $endBracket{
  154.                 $prevToken $phpcsFile->findPrevious(T_WHITESPACE($bracket - 1)nulltrue);
  155.                 $prevCode  $tokens[$prevToken]['code'];
  156.  
  157.                 if ($prevCode === T_ISSET{
  158.                     // This operation is inside an isset() call, but has
  159.                     // no bracket of it's own.
  160.                     break;
  161.                 }
  162.  
  163.                 if ($prevCode === T_STRING || $prevCode === T_SWITCH{
  164.                     // We allow simple operations to not be bracketed.
  165.                     // For example, ceil($one / $two).
  166.                     for ($prev ($stackPtr - 1)$prev $bracket$prev--{
  167.                         if (in_array($tokens[$prev]['code']$allowed=== true{
  168.                             continue;
  169.                         }
  170.  
  171.                         if ($tokens[$prev]['code'=== T_CLOSE_PARENTHESIS{
  172.                             $prev $tokens[$prev]['parenthesis_opener'];
  173.                         else {
  174.                             break;
  175.                         }
  176.                     }
  177.  
  178.                     if ($prev !== $bracket{
  179.                         break;
  180.                     }
  181.  
  182.                     for ($next ($stackPtr + 1)$next $endBracket$next++{
  183.                         if (in_array($tokens[$next]['code']$allowed=== true{
  184.                             continue;
  185.                         }
  186.  
  187.                         if ($tokens[$next]['code'=== T_OPEN_PARENTHESIS{
  188.                             $next $tokens[$next]['parenthesis_closer'];
  189.                         else {
  190.                             break;
  191.                         }
  192.                     }
  193.  
  194.                     if ($next !== $endBracket{
  195.                         break;
  196.                     }
  197.                 }//end if
  198.  
  199.                 if (in_array($prevCodeTokens::$scopeOpeners=== true{
  200.                     // This operation is inside a control structure like FOREACH
  201.                     // or IF, but has no bracket of it's own.
  202.                     // The only control structure allowed to do this is SWITCH.
  203.                     if ($prevCode !== T_SWITCH{
  204.                         break;
  205.                     }
  206.                 }
  207.  
  208.                 if ($prevCode === T_OPEN_PARENTHESIS{
  209.                     // These are two open parenthesis in a row. If the current
  210.                     // one doesn't enclose the operator, go to the previous one.
  211.                     if ($endBracket $stackPtr{
  212.                         continue;
  213.                     }
  214.                 }
  215.  
  216.                 $lastBracket $bracket;
  217.                 break;
  218.             }//end foreach
  219.         }//end if
  220.  
  221.         if ($lastBracket === false{
  222.             // It is not in a bracketed statement at all.
  223.             $this->addMissingBracketsError($phpcsFile$stackPtr);
  224.             return;
  225.         else if ($tokens[$lastBracket]['parenthesis_closer'$stackPtr{
  226.             // There are a set of brackets in front of it that don't include it.
  227.             $this->addMissingBracketsError($phpcsFile$stackPtr);
  228.             return;
  229.         else {
  230.             // We are enclosed in a set of bracket, so the last thing to
  231.             // check is that we are not also enclosed in square brackets
  232.             // like this: ($array[$index + 1]), which is invalid.
  233.             $brackets = array(
  234.                          T_OPEN_SQUARE_BRACKET,
  235.                          T_CLOSE_SQUARE_BRACKET,
  236.                         );
  237.  
  238.             $squareBracket $phpcsFile->findPrevious($brackets($stackPtr - 1)$lastBracket);
  239.             if ($squareBracket !== false && $tokens[$squareBracket]['code'=== T_OPEN_SQUARE_BRACKET{
  240.                 $closeSquareBracket $phpcsFile->findNext($brackets($stackPtr + 1));
  241.                 if ($closeSquareBracket !== false && $tokens[$closeSquareBracket]['code'=== T_CLOSE_SQUARE_BRACKET{
  242.                     $this->addMissingBracketsError($phpcsFile$stackPtr);
  243.                 }
  244.             }
  245.  
  246.             return;
  247.         }//end if
  248.  
  249.         $lastAssignment $phpcsFile->findPrevious(Tokens::$assignmentTokens$stackPtrnullfalsenulltrue);
  250.         if ($lastAssignment !== false && $lastAssignment $lastBracket{
  251.             $this->addMissingBracketsError($phpcsFile$stackPtr);
  252.         }
  253.  
  254.     }//end process()
  255.  
  256.  
  257.     /**
  258.      * Add and fix the missing brackets error.
  259.      *
  260.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  261.      * @param int                         $stackPtr  The position of the current token in the
  262.      *                                                stack passed in $tokens.
  263.      *
  264.      * @return void 
  265.      */
  266.     public function addMissingBracketsError($phpcsFile$stackPtr)
  267.     {
  268.         $error 'Arithmetic operation must be bracketed';
  269.         $fix   $phpcsFile->addFixableError($error$stackPtr'MissingBrackets');
  270.  
  271.         if ($fix === false{
  272.             return;
  273.         }
  274.  
  275.         $tokens $phpcsFile->getTokens();
  276.  
  277.         $allowed = array(
  278.                     T_VARIABLE        => true,
  279.                     T_LNUMBER         => true,
  280.                     T_DNUMBER         => true,
  281.                     T_STRING          => true,
  282.                     T_WHITESPACE      => true,
  283.                     T_NS_SEPARATOR    => true,
  284.                     T_THIS            => true,
  285.                     T_SELF            => true,
  286.                     T_OBJECT_OPERATOR => true,
  287.                     T_DOUBLE_COLON    => true,
  288.                     T_MODULUS         => true,
  289.                     T_ISSET           => true,
  290.                     T_ARRAY           => true,
  291.                     T_NONE            => true,
  292.                    );
  293.  
  294.         // Find the first token in the expression.
  295.         for ($before ($stackPtr - 1)$before > 0; $before--{
  296.             // Special case for plus operators because we can't tell if they are used
  297.             // for addition or string contact. So assume string concat to be safe.
  298.             if ($phpcsFile->tokenizerType === 'JS' && $tokens[$before]['code'=== T_PLUS{
  299.                 break;
  300.             }
  301.  
  302.             if (isset(Tokens::$emptyTokens[$tokens[$before]['code']]=== true
  303.                 || isset(Tokens::$operators[$tokens[$before]['code']]=== true
  304.                 || isset(Tokens::$castTokens[$tokens[$before]['code']]=== true
  305.                 || isset($allowed[$tokens[$before]['code']]=== true
  306.             {
  307.                 continue;
  308.             }
  309.  
  310.             if ($tokens[$before]['code'=== T_CLOSE_PARENTHESIS{
  311.                 $before $tokens[$before]['parenthesis_opener'];
  312.                 continue;
  313.             }
  314.  
  315.             if ($tokens[$before]['code'=== T_CLOSE_SQUARE_BRACKET{
  316.                 $before $tokens[$before]['bracket_opener'];
  317.                 continue;
  318.             }
  319.  
  320.             if ($tokens[$before]['code'=== T_CLOSE_SHORT_ARRAY{
  321.                 $before $tokens[$before]['bracket_opener'];
  322.                 continue;
  323.             }
  324.  
  325.             break;
  326.         }//end for
  327.  
  328.         $before $phpcsFile->findNext(Tokens::$emptyTokens($before + 1)nulltrue);
  329.  
  330.         // Find the last token in the expression.
  331.         for ($after ($stackPtr + 1)$after $phpcsFile->numTokens; $after++{
  332.             // Special case for plus operators because we can't tell if they are used
  333.             // for addition or string contact. So assume string concat to be safe.
  334.             if ($phpcsFile->tokenizerType === 'JS' && $tokens[$after]['code'=== T_PLUS{
  335.                 break;
  336.             }
  337.  
  338.             if (isset(Tokens::$emptyTokens[$tokens[$after]['code']]=== true
  339.                 || isset(Tokens::$operators[$tokens[$after]['code']]=== true
  340.                 || isset(Tokens::$castTokens[$tokens[$after]['code']]=== true
  341.                 || isset($allowed[$tokens[$after]['code']]=== true
  342.             {
  343.                 continue;
  344.             }
  345.  
  346.             if ($tokens[$after]['code'=== T_OPEN_PARENTHESIS{
  347.                 $after $tokens[$after]['parenthesis_closer'];
  348.                 continue;
  349.             }
  350.  
  351.             if ($tokens[$after]['code'=== T_OPEN_SQUARE_BRACKET{
  352.                 $after $tokens[$after]['bracket_closer'];
  353.                 continue;
  354.             }
  355.  
  356.             if ($tokens[$after]['code'=== T_OPEN_SHORT_ARRAY{
  357.                 $after $tokens[$after]['bracket_closer'];
  358.                 continue;
  359.             }
  360.  
  361.             break;
  362.         }//end for
  363.  
  364.         $after $phpcsFile->findPrevious(Tokens::$emptyTokens($after - 1)nulltrue);
  365.  
  366.         // Can only fix this error if both tokens are available for fixing.
  367.         // Adding one bracket without the other will create parse errors.
  368.         $phpcsFile->fixer->beginChangeset();
  369.         $phpcsFile->fixer->replaceToken($before'('.$tokens[$before]['content']);
  370.         $phpcsFile->fixer->replaceToken($after$tokens[$after]['content'].')');
  371.         $phpcsFile->fixer->endChangeset();
  372.  
  373.     }//end addMissingBracketsError()
  374.  
  375.  
  376. }//end class

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