/** * Processes this sniff, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The current file being checked. * @param int $stackPtr The position of the current token in * the stack passed in $tokens. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); // Skip default values in function declarations. if ($tokens[$stackPtr]['code'] === T_EQUAL || $tokens[$stackPtr]['code'] === T_MINUS) { if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { $parenthesis = array_keys($tokens[$stackPtr]['nested_parenthesis']); $bracket = array_pop($parenthesis); if (isset($tokens[$bracket]['parenthesis_owner']) === true) { $function = $tokens[$bracket]['parenthesis_owner']; if ($tokens[$function]['code'] === T_FUNCTION || $tokens[$function]['code'] === T_CLOSURE) { return; } } } } if ($tokens[$stackPtr]['code'] === T_EQUAL) { // Skip for '=&' case. if (isset($tokens[$stackPtr + 1]) === true && $tokens[$stackPtr + 1]['code'] === T_BITWISE_AND) { return; } } // Skip short ternary such as: "$foo = $bar ?: true;". if ($tokens[$stackPtr]['code'] === T_INLINE_THEN && $tokens[$stackPtr + 1]['code'] === T_INLINE_ELSE || $tokens[$stackPtr - 1]['code'] === T_INLINE_THEN && $tokens[$stackPtr]['code'] === T_INLINE_ELSE) { return; } if ($tokens[$stackPtr]['code'] === T_BITWISE_AND) { // If it's not a reference, then we expect one space either side of the // bitwise operator. if ($phpcsFile->isReference($stackPtr) === true) { return; } // Check there is one space before the & operator. if ($tokens[$stackPtr - 1]['code'] !== T_WHITESPACE) { $error = 'Expected 1 space before "&" operator; 0 found'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBeforeAmp'); if ($fix === true) { $phpcsFile->fixer->addContentBefore($stackPtr, ' '); } $phpcsFile->recordMetric($stackPtr, 'Space before operator', 0); } else { if ($tokens[$stackPtr - 2]['line'] !== $tokens[$stackPtr]['line']) { $found = 'newline'; } else { $found = $tokens[$stackPtr - 1]['length']; } $phpcsFile->recordMetric($stackPtr, 'Space before operator', $found); if ($found !== 1 && ($found !== 'newline' || $this->ignoreNewlines === false)) { $error = 'Expected 1 space before "&" operator; %s found'; $data = array($found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBeforeAmp', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($stackPtr - 1, ' '); } } } //end if // Check there is one space after the & operator. if ($tokens[$stackPtr + 1]['code'] !== T_WHITESPACE) { $error = 'Expected 1 space after "&" operator; 0 found'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceAfterAmp'); if ($fix === true) { $phpcsFile->fixer->addContent($stackPtr, ' '); } $phpcsFile->recordMetric($stackPtr, 'Space after operator', 0); } else { if ($tokens[$stackPtr + 2]['line'] !== $tokens[$stackPtr]['line']) { $found = 'newline'; } else { $found = $tokens[$stackPtr + 1]['length']; } $phpcsFile->recordMetric($stackPtr, 'Space after operator', $found); if ($found !== 1 && ($found !== 'newline' || $this->ignoreNewlines === false)) { $error = 'Expected 1 space after "&" operator; %s found'; $data = array($found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfterAmp', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($stackPtr + 1, ' '); } } } //end if return; } //end if if ($tokens[$stackPtr]['code'] === T_MINUS || $tokens[$stackPtr]['code'] === T_PLUS) { // Check minus spacing, but make sure we aren't just assigning // a minus value or returning one. $prev = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); if ($tokens[$prev]['code'] === T_RETURN) { // Just returning a negative value; eg. (return -1). return; } if (isset(Tokens::$operators[$tokens[$prev]['code']]) === true) { // Just trying to operate on a negative value; eg. ($var * -1). return; } if (isset(Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true) { // Just trying to compare a negative value; eg. ($var === -1). return; } if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { // Just trying to compare a negative value; eg. ($var || -1 === $b). return; } if (isset(Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true) { // Just trying to assign a negative value; eg. ($var = -1). return; } // A list of tokens that indicate that the token is not // part of an arithmetic operation. $invalidTokens = array(T_COMMA => true, T_OPEN_PARENTHESIS => true, T_OPEN_SQUARE_BRACKET => true, T_OPEN_SHORT_ARRAY => true, T_DOUBLE_ARROW => true, T_COLON => true, T_INLINE_THEN => true, T_INLINE_ELSE => true, T_CASE => true); if (isset($invalidTokens[$tokens[$prev]['code']]) === true) { // Just trying to use a negative value; eg. myFunction($var, -2). return; } } //end if $operator = $tokens[$stackPtr]['content']; if ($tokens[$stackPtr - 1]['code'] !== T_WHITESPACE) { $error = "Expected 1 space before \"{$operator}\"; 0 found"; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBefore'); if ($fix === true) { $phpcsFile->fixer->addContentBefore($stackPtr, ' '); } $phpcsFile->recordMetric($stackPtr, 'Space before operator', 0); } else { if (isset(Tokens::$assignmentTokens[$tokens[$stackPtr]['code']]) === false) { // Don't throw an error for assignments, because other standards allow // multiple spaces there to align multiple assignments. if ($tokens[$stackPtr - 2]['line'] !== $tokens[$stackPtr]['line']) { $found = 'newline'; } else { $found = $tokens[$stackPtr - 1]['length']; } $phpcsFile->recordMetric($stackPtr, 'Space before operator', $found); if ($found !== 1 && ($found !== 'newline' || $this->ignoreNewlines === false)) { $error = 'Expected 1 space before "%s"; %s found'; $data = array($operator, $found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); if ($found === 'newline') { $i = $stackPtr - 2; while ($tokens[$i]['code'] === T_WHITESPACE) { $phpcsFile->fixer->replaceToken($i, ''); $i--; } } $phpcsFile->fixer->replaceToken($stackPtr - 1, ' '); $phpcsFile->fixer->endChangeset(); } } //end if } } //end if if (isset($tokens[$stackPtr + 1]) === false) { return; } if ($tokens[$stackPtr + 1]['code'] !== T_WHITESPACE) { $error = "Expected 1 space after \"{$operator}\"; 0 found"; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceAfter'); if ($fix === true) { $phpcsFile->fixer->addContent($stackPtr, ' '); } $phpcsFile->recordMetric($stackPtr, 'Space after operator', 0); } else { if (isset($tokens[$stackPtr + 2]) === true && $tokens[$stackPtr + 2]['line'] !== $tokens[$stackPtr]['line']) { $found = 'newline'; } else { $found = $tokens[$stackPtr + 1]['length']; } $phpcsFile->recordMetric($stackPtr, 'Space after operator', $found); if ($found !== 1 && ($found !== 'newline' || $this->ignoreNewlines === false)) { $error = 'Expected 1 space after "%s"; %s found'; $data = array($operator, $found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfter', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($stackPtr + 1, ' '); } } } //end if }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. * @param int $stackPtr The position of the current token in the * stack passed in $tokens. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if ($phpcsFile->tokenizerType === 'JS' && $tokens[$stackPtr]['code'] === T_PLUS) { // JavaScript uses the plus operator for string concatenation as well // so we cannot accurately determine if it is a string concat or addition. // So just ignore it. return; } // If the & is a reference, then we don't want to check for brackets. if ($tokens[$stackPtr]['code'] === T_BITWISE_AND && $phpcsFile->isReference($stackPtr) === true) { return; } // There is one instance where brackets aren't needed, which involves // the minus sign being used to assign a negative number to a variable. if ($tokens[$stackPtr]['code'] === T_MINUS) { // Check to see if we are trying to return -n. $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true); if ($tokens[$prev]['code'] === T_RETURN) { return; } $number = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true); if ($tokens[$number]['code'] === T_LNUMBER || $tokens[$number]['code'] === T_DNUMBER) { $previous = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); if ($previous !== false) { $isAssignment = in_array($tokens[$previous]['code'], Tokens::$assignmentTokens); $isEquality = in_array($tokens[$previous]['code'], Tokens::$equalityTokens); $isComparison = in_array($tokens[$previous]['code'], Tokens::$comparisonTokens); if ($isAssignment === true || $isEquality === true || $isComparison === true) { // This is a negative assignment or comparison. // We need to check that the minus and the number are // adjacent. if ($number - $stackPtr !== 1) { $error = 'No space allowed between minus sign and number'; $phpcsFile->addError($error, $stackPtr, 'SpacingAfterMinus'); } return; } } } } //end if // Tokens that are allowed inside a bracketed operation. $allowed = array(T_VARIABLE, T_LNUMBER, T_DNUMBER, T_STRING, T_WHITESPACE, T_THIS, T_SELF, T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_OPEN_SQUARE_BRACKET, T_CLOSE_SQUARE_BRACKET, T_MODULUS, T_NONE); $allowed += Tokens::$operators; $lastBracket = false; if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { $parenthesis = array_reverse($tokens[$stackPtr]['nested_parenthesis'], true); foreach ($parenthesis as $bracket => $endBracket) { $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, $bracket - 1, null, true); $prevCode = $tokens[$prevToken]['code']; if ($prevCode === T_ISSET) { // This operation is inside an isset() call, but has // no bracket of it's own. break; } if ($prevCode === T_STRING || $prevCode === T_SWITCH) { // We allow simple operations to not be bracketed. // For example, ceil($one / $two). for ($prev = $stackPtr - 1; $prev > $bracket; $prev--) { if (in_array($tokens[$prev]['code'], $allowed) === true) { continue; } if ($tokens[$prev]['code'] === T_CLOSE_PARENTHESIS) { $prev = $tokens[$prev]['parenthesis_opener']; } else { break; } } if ($prev !== $bracket) { break; } for ($next = $stackPtr + 1; $next < $endBracket; $next++) { if (in_array($tokens[$next]['code'], $allowed) === true) { continue; } if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS) { $next = $tokens[$next]['parenthesis_closer']; } else { break; } } if ($next !== $endBracket) { break; } } //end if if (in_array($prevCode, Tokens::$scopeOpeners) === true) { // This operation is inside a control structure like FOREACH // or IF, but has no bracket of it's own. // The only control structure allowed to do this is SWITCH. if ($prevCode !== T_SWITCH) { break; } } if ($prevCode === T_OPEN_PARENTHESIS) { // These are two open parenthesis in a row. If the current // one doesn't enclose the operator, go to the previous one. if ($endBracket < $stackPtr) { continue; } } $lastBracket = $bracket; break; } //end foreach } //end if if ($lastBracket === false) { // It is not in a bracketed statement at all. $previousToken = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true, null, true); if ($previousToken !== false) { // A list of tokens that indicate that the token is not // part of an arithmetic operation. $invalidTokens = array(T_COMMA, T_COLON, T_OPEN_PARENTHESIS, T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY, T_CASE); if (in_array($tokens[$previousToken]['code'], $invalidTokens) === false) { $this->addMissingBracketsError($phpcsFile, $stackPtr); } return; } } else { if ($tokens[$lastBracket]['parenthesis_closer'] < $stackPtr) { // There are a set of brackets in front of it that don't include it. $this->addMissingBracketsError($phpcsFile, $stackPtr); return; } else { // We are enclosed in a set of bracket, so the last thing to // check is that we are not also enclosed in square brackets // like this: ($array[$index + 1]), which is invalid. $brackets = array(T_OPEN_SQUARE_BRACKET, T_CLOSE_SQUARE_BRACKET); $squareBracket = $phpcsFile->findPrevious($brackets, $stackPtr - 1, $lastBracket); if ($squareBracket !== false && $tokens[$squareBracket]['code'] === T_OPEN_SQUARE_BRACKET) { $closeSquareBracket = $phpcsFile->findNext($brackets, $stackPtr + 1); if ($closeSquareBracket !== false && $tokens[$closeSquareBracket]['code'] === T_CLOSE_SQUARE_BRACKET) { $this->addMissingBracketsError($phpcsFile, $stackPtr); } } return; } } //end if $lastAssignment = $phpcsFile->findPrevious(Tokens::$assignmentTokens, $stackPtr, null, false, null, true); if ($lastAssignment !== false && $lastAssignment > $lastBracket) { $this->addMissingBracketsError($phpcsFile, $stackPtr); } }