@@ -33,6 +33,49 @@ public function register() {
3333 return array ( T_OPEN_PARENTHESIS );
3434 }
3535
36+ /**
37+ * Detect parentheses that only wrap a simple comparison within a logical expression.
38+ *
39+ * Example: ( $start_year <= $year ) where the surrounding tokens are ||/&& or parentheses.
40+ *
41+ * @param File $phpcsFile The file being scanned.
42+ * @param int $openParen Opening parenthesis token.
43+ * @param int $closeParen Closing parenthesis token.
44+ * @param int $prevToken Token before the opening parenthesis.
45+ * @param int $afterClose Token after the closing parenthesis.
46+ *
47+ * @return bool
48+ */
49+ private function isRedundantComparisonInLogicalExpression ( File $ phpcsFile , $ openParen , $ closeParen , $ prevToken , $ afterClose ) {
50+ $ tokens = $ phpcsFile ->getTokens ();
51+
52+ $ validPreceding = array (
53+ T_BOOLEAN_AND ,
54+ T_BOOLEAN_OR ,
55+ T_LOGICAL_AND ,
56+ T_LOGICAL_OR ,
57+ T_OPEN_PARENTHESIS ,
58+ );
59+
60+ if ( ! in_array ( $ tokens [ $ prevToken ]['code ' ], $ validPreceding , true ) ) {
61+ return false ;
62+ }
63+
64+ $ validFollowing = array (
65+ T_BOOLEAN_AND ,
66+ T_BOOLEAN_OR ,
67+ T_LOGICAL_AND ,
68+ T_LOGICAL_OR ,
69+ T_CLOSE_PARENTHESIS ,
70+ );
71+
72+ if ( ! in_array ( $ tokens [ $ afterClose ]['code ' ], $ validFollowing , true ) ) {
73+ return false ;
74+ }
75+
76+ return $ this ->isSimpleComparisonExpression ( $ phpcsFile , $ openParen , $ closeParen );
77+ }
78+
3679 /**
3780 * Processes this test, when one of its tokens is encountered.
3881 *
@@ -72,6 +115,12 @@ public function process( File $phpcsFile, $stackPtr ) {
72115 return ;
73116 }
74117
118+ // Check for redundant parentheses around a simple comparison inside a logical expression.
119+ if ( $ this ->isRedundantComparisonInLogicalExpression ( $ phpcsFile , $ stackPtr , $ closeParen , $ prevToken , $ afterClose ) ) {
120+ $ this ->reportAndFix ( $ phpcsFile , $ stackPtr , $ closeParen );
121+ return ;
122+ }
123+
75124 // We only care about parentheses after an assignment operator, array arrow, or return.
76125 $ validPrecedingTokens = array (
77126 T_EQUAL ,
@@ -213,6 +262,24 @@ private function isSimpleComparisonExpression( File $phpcsFile, $openParen, $clo
213262 $ comparisonCount = 0 ;
214263 $ logicalCount = 0 ;
215264 $ nestedParenDepth = 0 ;
265+ $ arithmeticCount = 0 ;
266+
267+ $ comparisonTokens = array (
268+ T_IS_EQUAL ,
269+ T_IS_NOT_EQUAL ,
270+ T_IS_IDENTICAL ,
271+ T_IS_NOT_IDENTICAL ,
272+ T_IS_SMALLER_OR_EQUAL ,
273+ T_IS_GREATER_OR_EQUAL ,
274+ );
275+
276+ $ arithmeticTokens = array (
277+ T_PLUS ,
278+ T_MINUS ,
279+ T_MULTIPLY ,
280+ T_DIVIDE ,
281+ T_MODULUS ,
282+ );
216283
217284 for ( $ i = $ openParen + 1 ; $ i < $ closeParen ; $ i ++ ) {
218285 $ code = $ tokens [ $ i ]['code ' ];
@@ -233,18 +300,23 @@ private function isSimpleComparisonExpression( File $phpcsFile, $openParen, $clo
233300 }
234301
235302 // Count comparison operators.
236- if ( in_array ( $ code , array ( T_IS_EQUAL , T_IS_NOT_EQUAL , T_IS_IDENTICAL , T_IS_NOT_IDENTICAL ) , true ) ) {
303+ if ( in_array ( $ code , $ comparisonTokens , true ) ) {
237304 ++$ comparisonCount ;
238305 }
239306
240307 // Check for logical operators - if present, parentheses might be needed.
241308 if ( in_array ( $ code , array ( T_BOOLEAN_AND , T_BOOLEAN_OR , T_LOGICAL_AND , T_LOGICAL_OR ), true ) ) {
242309 ++$ logicalCount ;
243310 }
311+
312+ // Arithmetic on either side means the grouping might be required.
313+ if ( in_array ( $ code , $ arithmeticTokens , true ) ) {
314+ ++$ arithmeticCount ;
315+ }
244316 }
245317
246318 // Simple comparison: exactly one comparison operator and no logical operators.
247- return 1 === $ comparisonCount && 0 === $ logicalCount ;
319+ return 1 === $ comparisonCount && 0 === $ logicalCount && 0 === $ arithmeticCount ;
248320 }
249321
250322 /**
0 commit comments