Skip to content

Commit 8642e1f

Browse files
SONARPY-700 Fix FP on S905 when contextlib.supress is used (SonarSource#858)
1 parent c0ca6c8 commit 8642e1f

2 files changed

Lines changed: 28 additions & 2 deletions

File tree

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.util.Arrays;
2323
import java.util.List;
24+
import java.util.Objects;
2425
import java.util.Optional;
2526
import java.util.stream.Collectors;
2627
import java.util.stream.Stream;
@@ -45,6 +46,8 @@
4546
import org.sonar.plugins.python.api.tree.Tree;
4647
import org.sonar.plugins.python.api.tree.Tree.Kind;
4748
import org.sonar.plugins.python.api.tree.UnaryExpression;
49+
import org.sonar.plugins.python.api.tree.WithItem;
50+
import org.sonar.plugins.python.api.tree.WithStatement;
4851
import org.sonar.python.tree.TreeUtils;
4952

5053
@Rule(key = "S905")
@@ -110,9 +113,26 @@ private static void checkNode(SubscriptionContext ctx) {
110113
if (parent == null || !parent.is(Kind.EXPRESSION_STMT)) {
111114
return;
112115
}
116+
if (isWithinContextlibSuppress(tree)) {
117+
return;
118+
}
113119
ctx.addIssue(tree, MESSAGE);
114120
}
115121

122+
private static boolean isWithinContextlibSuppress(Tree tree) {
123+
Tree withParent = TreeUtils.firstAncestorOfKind(tree, Kind.WITH_STMT);
124+
if (withParent != null) {
125+
WithStatement withStatement = (WithStatement) withParent;
126+
return withStatement.withItems().stream()
127+
.map(WithItem::test)
128+
.filter(item -> item.is(Kind.CALL_EXPR))
129+
.map(item -> ((CallExpression) item).calleeSymbol())
130+
.filter(Objects::nonNull)
131+
.anyMatch(s -> "contextlib.suppress".equals(s.fullyQualifiedName()));
132+
}
133+
return false;
134+
}
135+
116136
private static boolean isBooleanExpressionWithCalls(Tree tree) {
117137
return (tree.is(Kind.AND) || tree.is(Kind.OR) || tree.is(Kind.NOT)) && (TreeUtils.hasDescendant(tree, t -> t.is(Kind.CALL_EXPR)));
118138
}
@@ -172,8 +192,8 @@ private void checkBinaryExpression(SubscriptionContext ctx) {
172192

173193
private static boolean couldBePython2PrintStatement(BinaryExpression binaryExpression) {
174194
return TreeUtils.hasDescendant(binaryExpression, t -> t.is(Kind.CALL_EXPR)
175-
&& ((CallExpression) t).callee().is(Kind.NAME)
176-
&& ((Name) ((CallExpression) t).callee()).name().equals("print"));
195+
&& ((CallExpression) t).callee().is(Kind.NAME)
196+
&& ((Name) ((CallExpression) t).callee()).name().equals("print"));
177197
}
178198

179199
private void checkUnaryExpression(SubscriptionContext ctx) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ def pointless_statement_avoid_unused_import():
130130
numpy # Noncompliant
131131
numpy # Noncompliant
132132
pass
133+
134+
def no_issue_within_contextlib_suppress(a):
135+
from contextlib import suppress
136+
with suppress(TypeError):
137+
a + '' # OK
138+
return a

0 commit comments

Comments
 (0)