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), null, true);
  72.             if ($tokens[$prev]['code'] === T_RETURN) {
  73.                 return;
  74.             }
  75.  
  76.             $number = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
  77.             if ($tokens[$number]['code'] === T_LNUMBER || $tokens[$number]['code'] === T_DNUMBER) {
  78.                 $previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
  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), null, true, null, true);
  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), null, true);
  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($prevCode, Tokens::$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, $stackPtr, null, false, null, true);
  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), null, true);
  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), null, true);
  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.