diff --git a/package.xml b/package.xml index b84d69730c..0d7a071411 100644 --- a/package.xml +++ b/package.xml @@ -414,6 +414,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -550,6 +551,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/MixedBooleanOperatorSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/MixedBooleanOperatorSniff.php new file mode 100644 index 0000000000..5fd9d113ba --- /dev/null +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/MixedBooleanOperatorSniff.php @@ -0,0 +1,104 @@ + + * $var = true && true || true; + * + * + * @author Tim Duesterhus + * @copyright 2021 WoltLab GmbH. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +class MixedBooleanOperatorSniff implements Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [ + T_BOOLEAN_OR, + T_BOOLEAN_AND, + ]; + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\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(); + $token = $tokens[$stackPtr]; + + $start = $phpcsFile->findStartOfStatement($stackPtr); + + if ($token['code'] === T_BOOLEAN_AND) { + $search = T_BOOLEAN_OR; + } else if ($token['code'] === T_BOOLEAN_OR) { + $search = T_BOOLEAN_AND; + } else { + throw new \LogicException('Unreachable'); + } + + while (true) { + $previous = $phpcsFile->findPrevious( + [ + $search, + T_OPEN_PARENTHESIS, + T_OPEN_SQUARE_BRACKET, + T_CLOSE_PARENTHESIS, + T_CLOSE_SQUARE_BRACKET, + ], + $stackPtr, + $start + ); + + if ($previous === false) { + break; + } + + if ($tokens[$previous]['code'] === T_OPEN_PARENTHESIS + || $tokens[$previous]['code'] === T_OPEN_SQUARE_BRACKET + ) { + // We halt if we reach the opening parens / bracket of the boolean operator. + return; + } else if ($tokens[$previous]['code'] === T_CLOSE_PARENTHESIS) { + // We skip the content of nested parens. + $stackPtr = ($tokens[$previous]['parenthesis_opener'] - 1); + } else if ($tokens[$previous]['code'] === T_CLOSE_SQUARE_BRACKET) { + // We skip the content of nested brackets. + $stackPtr = ($tokens[$previous]['bracket_opener'] - 1); + } else if ($tokens[$previous]['code'] === $search) { + // We reached a mismatching operator, thus we must report the error. + $error = "Mixed '&&' and '||' within an expression without using parentheses."; + $phpcsFile->addError($error, $stackPtr, 'MissingParentheses', []); + return; + } else { + throw new \LogicException('Unreachable'); + } + }//end while + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Tests/CodeAnalysis/MixedBooleanOperatorUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/MixedBooleanOperatorUnitTest.inc new file mode 100644 index 0000000000..cc88ecd189 --- /dev/null +++ b/src/Standards/Generic/Tests/CodeAnalysis/MixedBooleanOperatorUnitTest.inc @@ -0,0 +1,41 @@ + + * @copyright 2021 WoltLab GmbH. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Tests\CodeAnalysis; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class MixedBooleanOperatorUnitTest extends AbstractSniffUnitTest +{ + + + /** + * Returns the lines where errors should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of errors that should occur on that line. + * + * @return array + */ + public function getErrorList() + { + return [ + 3 => 1, + 7 => 1, + 12 => 1, + 17 => 1, + 29 => 1, + 31 => 1, + 33 => 1, + 34 => 1, + 35 => 1, + 37 => 1, + 39 => 1, + 41 => 2, + ]; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + + }//end getWarningList() + + +}//end class