Skip to content

Commit 57349a0

Browse files
guillaume-dequennesonartech
authored andcommitted
SONARPY-4047 Extend S5714 to more bitwise boolean expression types (#1080)
GitOrigin-RevId: 5dd8a75e6de674990c541c2c789684c271ac57b5
1 parent 956b3c6 commit 57349a0

3 files changed

Lines changed: 111 additions & 2 deletions

File tree

python-checks/src/main/java/org/sonar/python/checks/BooleanExpressionInExceptCheck.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public class BooleanExpressionInExceptCheck extends PythonSubscriptionCheck {
4141
public static final String MESSAGE = "Rewrite this \"except\" expression as a tuple of exception classes.";
4242
public static final String QUICK_FIX_MESSAGE = "Replace with a tuple";
4343

44+
private static final Kind[] FLAGGED_BINARY_KINDS = {
45+
Kind.OR, Kind.AND, Kind.BITWISE_OR, Kind.BITWISE_AND
46+
};
47+
4448
@Override
4549
public void initialize(Context context) {
4650
context.registerSyntaxNodeConsumer(Kind.EXCEPT_CLAUSE, BooleanExpressionInExceptCheck::checkExceptClause);
@@ -52,7 +56,7 @@ private static void checkExceptClause(SubscriptionContext ctx) {
5256
Optional.of(except)
5357
.map(ExceptClause::exception)
5458
.map(Expressions::removeParentheses)
55-
.filter(exception -> exception.is(Kind.OR, Kind.AND))
59+
.filter(exception -> exception.is(FLAGGED_BINARY_KINDS))
5660
.ifPresent(exception -> {
5761
var issue = ctx.addIssue(exception, MESSAGE);
5862
addQuickFix(issue, exception);
@@ -61,7 +65,7 @@ private static void checkExceptClause(SubscriptionContext ctx) {
6165

6266
private static List<String> collectNames(Expression expression) {
6367
expression = Expressions.removeParentheses(expression);
64-
if (expression.is(Kind.OR, Kind.AND)) {
68+
if (expression.is(FLAGGED_BINARY_KINDS)) {
6569
var binaryExpression = (BinaryExpression) expression;
6670
var leftExceptions = collectNames(binaryExpression.leftOperand());
6771
var rightExceptions = collectNames(binaryExpression.rightOperand());

python-checks/src/test/java/org/sonar/python/checks/BooleanExpressionInExceptCheckTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,54 @@ void nestedQuickFixTest() {
7474
verifyQuickFix(before, after);
7575
}
7676

77+
@Test
78+
void bitwiseOrQuickFixTest() {
79+
var before = """
80+
try:
81+
foo()
82+
except ValueError | TypeError | SomeError:
83+
pass""";
84+
85+
var after = """
86+
try:
87+
foo()
88+
except (ValueError, TypeError, SomeError):
89+
pass""";
90+
verifyQuickFix(before, after);
91+
}
92+
93+
@Test
94+
void bitwiseAndQuickFixTest() {
95+
var before = """
96+
try:
97+
foo()
98+
except ValueError & TypeError & SomeError:
99+
pass""";
100+
101+
var after = """
102+
try:
103+
foo()
104+
except (ValueError, TypeError, SomeError):
105+
pass""";
106+
verifyQuickFix(before, after);
107+
}
108+
109+
@Test
110+
void mixedOperatorsQuickFixTest() {
111+
var before = """
112+
try:
113+
foo()
114+
except (ValueError | TypeError) and SomeError:
115+
pass""";
116+
117+
var after = """
118+
try:
119+
foo()
120+
except (ValueError, TypeError, SomeError):
121+
pass""";
122+
verifyQuickFix(before, after);
123+
}
124+
77125
@Test
78126
void noQuickFixTest() {
79127
var before = """

python-checks/src/test/resources/checks/booleanExpressionInExcept.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,60 @@
2929
pass
3030
except* (ValueError):
3131
pass
32+
33+
# === `|` bitwise OR ===
34+
try:
35+
foo()
36+
except ValueError | TypeError: # Noncompliant
37+
pass
38+
except ValueError | TypeError as e: # Noncompliant
39+
pass
40+
except (ValueError | TypeError): # Noncompliant
41+
pass
42+
except ConnectionError | TimeoutError | OSError: # Noncompliant
43+
pass
44+
45+
# === `&` bitwise AND ===
46+
try:
47+
foo()
48+
except ValueError & TypeError: # Noncompliant
49+
pass
50+
except ValueError & TypeError as e: # Noncompliant
51+
pass
52+
except (ValueError & TypeError): # Noncompliant
53+
pass
54+
55+
# === `except*` variants for bitwise operators ===
56+
try:
57+
foo()
58+
except* ValueError | TypeError: # Noncompliant
59+
pass
60+
except* ValueError & TypeError: # Noncompliant
61+
pass
62+
63+
# === Mixed: one valid handler, one with a bitwise operator ===
64+
try:
65+
foo()
66+
except TypeError:
67+
pass
68+
except ValueError | KeyError: # Noncompliant
69+
pass
70+
71+
try:
72+
foo()
73+
except* TypeError:
74+
pass
75+
except* ValueError | KeyError: # Noncompliant
76+
pass
77+
78+
# === Arithmetic operators are out of scope (e.g. tuple + tuple is valid Python) ===
79+
connection_errors = (ConnectionError, OSError)
80+
channel_errors = (KeyError,)
81+
try:
82+
foo()
83+
except connection_errors + channel_errors as exc:
84+
pass
85+
except connection_errors + channel_errors:
86+
pass
87+
except (connection_errors + channel_errors):
88+
pass

0 commit comments

Comments
 (0)