Skip to content

Commit c277861

Browse files
guillaume-dequennesonartech
authored andcommitted
SONARPY-2939 Fix FP for NotImplemented and NotImplementedError (#304)
GitOrigin-RevId: 8526b4fb4f3f34f7efc4a3c4363fbf8c1b7b5965
1 parent a8f3343 commit c277861

File tree

2 files changed

+30
-6
lines changed

2 files changed

+30
-6
lines changed

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
2525
import org.sonar.plugins.python.api.tree.ForStatement;
2626
import org.sonar.plugins.python.api.tree.FunctionDef;
27+
import org.sonar.plugins.python.api.tree.ReturnStatement;
2728
import org.sonar.plugins.python.api.tree.Statement;
2829
import org.sonar.plugins.python.api.tree.StatementList;
2930
import org.sonar.plugins.python.api.tree.Token;
@@ -33,19 +34,27 @@
3334
import org.sonar.plugins.python.api.tree.YieldStatement;
3435
import org.sonar.plugins.python.api.types.v2.ClassType;
3536
import org.sonar.plugins.python.api.types.v2.FunctionType;
37+
import org.sonar.plugins.python.api.types.v2.TriBool;
3638
import org.sonar.python.checks.utils.CheckUtils;
39+
import org.sonar.python.types.v2.TypeCheckBuilder;
3740

3841
@Rule(key = "S7503")
3942
public class AsyncFunctionNotAsyncCheck extends PythonSubscriptionCheck {
4043

4144
private static final String MESSAGE = "Use asynchronous features in this function or remove the `async` keyword.";
45+
private TypeCheckBuilder notImplementedTypeChecker;
4246

4347
@Override
4448
public void initialize(Context context) {
45-
context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, AsyncFunctionNotAsyncCheck::checkAsyncFunction);
49+
context.registerSyntaxNodeConsumer(Tree.Kind.FILE_INPUT, this::setupTypeChecks);
50+
context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, this::checkAsyncFunction);
4651
}
4752

48-
private static void checkAsyncFunction(SubscriptionContext ctx) {
53+
private void setupTypeChecks(SubscriptionContext ctx) {
54+
notImplementedTypeChecker = ctx.typeChecker().typeCheckBuilder().isTypeWithName("NotImplemented");
55+
}
56+
57+
private void checkAsyncFunction(SubscriptionContext ctx) {
4958
FunctionDef functionDef = (FunctionDef) ctx.syntaxNode();
5059

5160
Token asyncKeyword = functionDef.asyncKeyword();
@@ -60,9 +69,9 @@ private static void checkAsyncFunction(SubscriptionContext ctx) {
6069
}
6170
}
6271

63-
private static boolean isException(FunctionDef functionDef) {
72+
private boolean isException(FunctionDef functionDef) {
6473
return CheckUtils.isAbstract(functionDef) ||
65-
isEmptyFunction(functionDef.body()) ||
74+
isTrivialFunction(functionDef.body()) ||
6675
isDunderMethod(functionDef) ||
6776
!functionDef.decorators().isEmpty() ||
6877
mightBeOverridingMethod(functionDef);
@@ -73,15 +82,20 @@ private static boolean isDunderMethod(FunctionDef functionDef) {
7382
return methodName.startsWith("__");
7483
}
7584

76-
private static boolean isEmptyFunction(StatementList body) {
85+
private boolean isTrivialFunction(StatementList body) {
7786
for (Statement statement : body.statements()) {
78-
if (!CheckUtils.isEmptyStatement(statement)) {
87+
if (!CheckUtils.isEmptyStatement(statement) && !statement.is(Tree.Kind.RAISE_STMT) && !isReturnNotImplemented(statement)) {
7988
return false;
8089
}
8190
}
8291
return true;
8392
}
8493

94+
private boolean isReturnNotImplemented(Statement statement) {
95+
return statement.is(Tree.Kind.RETURN_STMT) &&
96+
((ReturnStatement) statement).expressions().stream().allMatch(e -> notImplementedTypeChecker.check(e.typeV2()) == TriBool.TRUE);
97+
}
98+
8599
private static boolean mightBeOverridingMethod(FunctionDef functionDef) {
86100
FunctionType functionType = (FunctionType) functionDef.name().typeV2();
87101
return functionType.owner() instanceof ClassType classType && (classType.hasUnresolvedHierarchy() || classType.inheritedMember(functionType.name()).isPresent());

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ async def async_generator_with_expression(): # Compliant
7777
return x
7878

7979
class AsyncClass:
80+
async def async_method_without_await_trivial(self): # Noncompliant
81+
return self.some_attribute
82+
8083
async def async_method_without_await(self): # Noncompliant
84+
do_something()
8185
return self.some_attribute
8286

8387
async def async_method_with_await(self): # Compliant
@@ -120,6 +124,12 @@ async def __unknown_dunder__(self):
120124
# Avoid risk of FPs
121125
pass
122126

127+
async def not_implemented_error(self):
128+
raise NotImplementedError("This method is not implemented")
129+
130+
async def not_implemented(self):
131+
return NotImplemented
132+
123133
class AsyncIterator:
124134
async def __aiter__(self):
125135
return self

0 commit comments

Comments
 (0)