Skip to content

Commit 38061b2

Browse files
committed
Add MixedBooleanOperatorSniff
1 parent c85ac6a commit 38061b2

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

package.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
405405
<file baseinstalldir="PHP/CodeSniffer" name="ForLoopShouldBeWhileLoopSniff.php" role="php" />
406406
<file baseinstalldir="PHP/CodeSniffer" name="ForLoopWithTestFunctionCallSniff.php" role="php" />
407407
<file baseinstalldir="PHP/CodeSniffer" name="JumbledIncrementerSniff.php" role="php" />
408+
<file baseinstalldir="PHP/CodeSniffer" name="MixedBooleanOperatorSniff.php" role="php" />
408409
<file baseinstalldir="PHP/CodeSniffer" name="UnconditionalIfStatementSniff.php" role="php" />
409410
<file baseinstalldir="PHP/CodeSniffer" name="UnnecessaryFinalModifierSniff.php" role="php" />
410411
<file baseinstalldir="PHP/CodeSniffer" name="UnusedFunctionParameterSniff.php" role="php" />
@@ -541,6 +542,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
541542
<file baseinstalldir="PHP/CodeSniffer" name="ForLoopWithTestFunctionCallUnitTest.php" role="test" />
542543
<file baseinstalldir="PHP/CodeSniffer" name="JumbledIncrementerUnitTest.inc" role="test" />
543544
<file baseinstalldir="PHP/CodeSniffer" name="JumbledIncrementerUnitTest.php" role="test" />
545+
<file baseinstalldir="PHP/CodeSniffer" name="MixedBooleanOperatorUnitTest.inc" role="test" />
546+
<file baseinstalldir="PHP/CodeSniffer" name="MixedBooleanOperatorUnitTest.php" role="test" />
544547
<file baseinstalldir="PHP/CodeSniffer" name="UnconditionalIfStatementUnitTest.inc" role="test" />
545548
<file baseinstalldir="PHP/CodeSniffer" name="UnconditionalIfStatementUnitTest.php" role="test" />
546549
<file baseinstalldir="PHP/CodeSniffer" name="UnnecessaryFinalModifierUnitTest.inc" role="test" />
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
/**
3+
* Detects mixed '&&' and '||' within a single expression, without making
4+
* precedence explicit using parentheses.
5+
*
6+
* <code>
7+
* $var = true && true || true;
8+
* </code>
9+
*
10+
* @author Tim Duesterhus <[email protected]>
11+
* @copyright 2021 WoltLab GmbH.
12+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13+
*/
14+
15+
namespace PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis;
16+
17+
use PHP_CodeSniffer\Files\File;
18+
use PHP_CodeSniffer\Sniffs\Sniff;
19+
20+
class MixedBooleanOperatorSniff implements Sniff
21+
{
22+
23+
24+
/**
25+
* Registers the tokens that this sniff wants to listen for.
26+
*
27+
* @return int[]
28+
*/
29+
public function register()
30+
{
31+
return [
32+
T_BOOLEAN_OR,
33+
T_BOOLEAN_AND,
34+
];
35+
36+
}//end register()
37+
38+
39+
/**
40+
* Processes this test, when one of its tokens is encountered.
41+
*
42+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
43+
* @param int $stackPtr The position of the current token
44+
* in the stack passed in $tokens.
45+
*
46+
* @return void
47+
*/
48+
public function process(File $phpcsFile, $stackPtr)
49+
{
50+
$tokens = $phpcsFile->getTokens();
51+
$token = $tokens[$stackPtr];
52+
53+
$start = $phpcsFile->findStartOfStatement($stackPtr);
54+
55+
if ($token['code'] === T_BOOLEAN_AND) {
56+
$search = T_BOOLEAN_OR;
57+
} else if ($token['code'] === T_BOOLEAN_OR) {
58+
$search = T_BOOLEAN_AND;
59+
} else {
60+
throw new \LogicException('Unreachable');
61+
}
62+
63+
while (true) {
64+
$previous = $phpcsFile->findPrevious(
65+
[
66+
$search,
67+
T_OPEN_PARENTHESIS,
68+
T_OPEN_SQUARE_BRACKET,
69+
T_CLOSE_PARENTHESIS,
70+
T_CLOSE_SQUARE_BRACKET,
71+
],
72+
$stackPtr,
73+
$start
74+
);
75+
76+
if ($previous === false) {
77+
break;
78+
}
79+
80+
if ($tokens[$previous]['code'] === T_OPEN_PARENTHESIS
81+
|| $tokens[$previous]['code'] === T_OPEN_SQUARE_BRACKET
82+
) {
83+
// We halt if we reach the opening parens / bracket of the boolean operator.
84+
return;
85+
} else if ($tokens[$previous]['code'] === T_CLOSE_PARENTHESIS) {
86+
// We skip the content of nested parens.
87+
$stackPtr = ($tokens[$previous]['parenthesis_opener'] - 1);
88+
} else if ($tokens[$previous]['code'] === T_CLOSE_SQUARE_BRACKET) {
89+
// We skip the content of nested brackets.
90+
$stackPtr = ($tokens[$previous]['bracket_opener'] - 1);
91+
} else if ($tokens[$previous]['code'] === $search) {
92+
// We reached a mismatching operator, thus we must report the error.
93+
$error = "Mixed '&&' and '||' within an expression without using parentheses.";
94+
$phpcsFile->addError($error, $stackPtr, 'MissingParentheses', []);
95+
return;
96+
} else {
97+
throw new \LogicException('Unreachable');
98+
}
99+
}//end while
100+
101+
}//end process()
102+
103+
104+
}//end class
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
if (true && true || true);
4+
if ((true && true) || true);
5+
if (true && (true || true));
6+
7+
$var = true && true || true;
8+
$var = (true && true) || true;
9+
$var = true && (true || true);
10+
11+
$complex = true && (true || true) && true;
12+
$complex = true && (true || true) || true;
13+
14+
if (
15+
true
16+
&& true
17+
|| true
18+
);
19+
20+
if (
21+
true
22+
&& (
23+
true
24+
|| true
25+
)
26+
);
27+
28+
if (true && foo(true || true));
29+
if (true && foo(true && true || true));
30+
if (true && $foo[true || true]);
31+
if (true && $foo[true && true || true]);
32+
33+
if (true && foo(true) || true);
34+
if (true && $foo[true] || true);
35+
if (true && foo($foo[true]) || true);
36+
37+
$foo[] = true && true || false;
38+
39+
foo([true && true || false]);
40+
41+
if (true && true || true && true);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
/**
3+
* Unit test class for the MixedBooleanOperator sniff.
4+
*
5+
* @author Tim Duesterhus <[email protected]>
6+
* @copyright 2021 WoltLab GmbH.
7+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8+
*/
9+
10+
namespace PHP_CodeSniffer\Standards\Generic\Tests\CodeAnalysis;
11+
12+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
13+
14+
class MixedBooleanOperatorUnitTest extends AbstractSniffUnitTest
15+
{
16+
17+
18+
/**
19+
* Returns the lines where errors should occur.
20+
*
21+
* The key of the array should represent the line number and the value
22+
* should represent the number of errors that should occur on that line.
23+
*
24+
* @return array<int, int>
25+
*/
26+
public function getErrorList()
27+
{
28+
return [
29+
3 => 1,
30+
7 => 1,
31+
12 => 1,
32+
17 => 1,
33+
29 => 1,
34+
31 => 1,
35+
33 => 1,
36+
34 => 1,
37+
35 => 1,
38+
37 => 1,
39+
39 => 1,
40+
41 => 2,
41+
];
42+
43+
}//end getErrorList()
44+
45+
46+
/**
47+
* Returns the lines where warnings should occur.
48+
*
49+
* The key of the array should represent the line number and the value
50+
* should represent the number of warnings that should occur on that line.
51+
*
52+
* @return array<int, int>
53+
*/
54+
public function getWarningList()
55+
{
56+
return [];
57+
58+
}//end getWarningList()
59+
60+
61+
}//end class

0 commit comments

Comments
 (0)