Skip to content

Commit 27fe31b

Browse files
SONARPY-1782 Migrate NonCallableCalledCheck to the new type model
1 parent a893b16 commit 27fe31b

File tree

5 files changed

+112
-17
lines changed

5 files changed

+112
-17
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"project:buildbot-0.8.6p1/buildbot/test/unit/test_revlinks.py": [
3+
81
4+
],
5+
"project:django-2.2.3/django/contrib/gis/geos/prototypes/io.py": [
6+
151,
7+
153,
8+
238,
9+
249,
10+
258,
11+
263,
12+
270,
13+
276,
14+
281,
15+
285
16+
],
17+
"project:django-2.2.3/django/db/transaction.py": [
18+
297
19+
],
20+
"project:django-2.2.3/django/test/utils.py": [
21+
789
22+
],
23+
"project:pecos/examples/qp2q/preprocessing/session_data_processing.py": [
24+
34
25+
],
26+
"project:pecos/examples/qp2q/preprocessing/sparse_data_processing.py": [
27+
40
28+
],
29+
"project:tensorflow/python/ops/control_flow_util_v2.py": [
30+
255
31+
],
32+
"project:tensorflow/python/training/tracking/util.py": [
33+
77
34+
]
35+
}
Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
{
2-
'calibre:src/calibre/ebooks/rtf2xml/field_strings.py':[
3-
334,
2+
"calibre:src/calibre/ebooks/mobi/writer2/main.py": [
3+
452
44
],
5+
"calibre:src/calibre/ebooks/mobi/writer8/index.py": [
6+
228
7+
],
8+
"calibre:src/calibre/ebooks/mobi/writer8/main.py": [
9+
381,
10+
382,
11+
492
12+
],
13+
"calibre:src/calibre/ebooks/mobi/writer8/mobi.py": [
14+
323
15+
],
16+
"calibre:src/calibre/srv/tests/loop.py": [
17+
41,
18+
43,
19+
43,
20+
47
21+
],
22+
"calibre:src/calibre/utils/matcher.py": [
23+
354
24+
]
525
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"salt:salt/config/schemas/ssh.py": [
3+
65
4+
]
5+
}

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,52 @@
2121

2222
import javax.annotation.Nullable;
2323
import org.sonar.check.Rule;
24+
import org.sonar.plugins.python.api.LocationInFile;
25+
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
26+
import org.sonar.plugins.python.api.tree.CallExpression;
27+
import org.sonar.plugins.python.api.tree.Expression;
28+
import org.sonar.plugins.python.api.tree.Tree;
2429
import org.sonar.plugins.python.api.types.InferredType;
30+
import org.sonar.python.types.InferredTypes;
31+
import org.sonar.python.types.v2.PythonType;
32+
import org.sonar.python.types.v2.TriBool;
33+
34+
import static org.sonar.python.tree.TreeUtils.nameFromExpression;
35+
import static org.sonar.python.types.InferredTypes.typeClassLocation;
2536

2637
@Rule(key = "S5756")
27-
public class NonCallableCalledCheck extends NonCallableCalled {
38+
public class NonCallableCalledCheck extends PythonSubscriptionCheck {
2839

2940
@Override
30-
public boolean isNonCallableType(InferredType type) {
31-
return !type.canHaveMember("__call__");
41+
public void initialize(Context context) {
42+
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> {
43+
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
44+
Expression callee = callExpression.callee();
45+
InferredType calleeType = callee.type();
46+
PythonType typeV2 = callee.typeV2();
47+
if (isNonCallableType(typeV2)) {
48+
String name = nameFromExpression(callee);
49+
PreciseIssue preciseIssue = ctx.addIssue(callee, message(calleeType, name));
50+
LocationInFile location = typeClassLocation(calleeType);
51+
if (location != null) {
52+
preciseIssue.secondary(location, "Definition.");
53+
}
54+
}
55+
});
56+
}
57+
58+
protected static String addTypeName(InferredType type) {
59+
String typeName = InferredTypes.typeName(type);
60+
if (typeName != null) {
61+
return " has type " + typeName + " and it";
62+
}
63+
return "";
64+
}
65+
66+
public boolean isNonCallableType(PythonType type) {
67+
return type.hasMember("__call__") == TriBool.FALSE;
3268
}
3369

34-
@Override
3570
public String message(InferredType calleeType, @Nullable String name) {
3671
if (name != null) {
3772
return String.format("Fix this call; \"%s\"%s is not callable.", name, addTypeName(calleeType));

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,32 @@ def call_noncallable(p):
2222
list_var() # Noncompliant
2323

2424
tuple_var = ()
25-
tuple_var() # Noncompliant
25+
tuple_var() # FN
2626

2727
dict_var = {}
28-
dict_var() # Noncompliant
28+
dict_var() # FN
2929

3030
set_var = set()
31-
set_var() # Noncompliant
31+
set_var() # FN
3232

3333
frozenset_var = frozenset()
34-
frozenset_var() # Noncompliant
34+
frozenset_var() # FN
3535

3636
if p:
3737
x = 42
3838
else:
3939
x = 'str'
40-
x() # Noncompliant {{Fix this call; "x" is not callable.}}
40+
x() # FN: multiple assignment not handled
4141

4242
def flow_sensitivity():
4343
my_var = "hello"
4444
my_var = 42
45-
my_var() # Noncompliant
45+
my_var() # FN: multiple assignment not handled
4646

4747
my_other_var = func
4848
my_other_var() # OK
4949
my_other_var = 42
50-
my_other_var() # Noncompliant
50+
my_other_var() # FN: multiple assignment not handled
5151

5252
def flow_sensitivity_nested_try_except():
5353
def func_with_try_except():
@@ -59,7 +59,7 @@ def func_with_try_except():
5959
def other_func():
6060
my_var = "hello"
6161
my_var = 42
62-
my_var() # Noncompliant
62+
my_var() # FN: multiple assignments
6363

6464
def member_access():
6565
my_callable = MyCallable()
@@ -69,16 +69,16 @@ def member_access():
6969
def types_from_typeshed(foo):
7070
from math import acos
7171
from functools import wraps
72-
acos(42)() # Noncompliant {{Fix this call; this expression has type float and it is not callable.}}
73-
# ^^^^^^^^
72+
acos(42)() # FN: declared return type of Typeshed
7473
wraps(func)(foo) # OK, wraps returns a Callable
7574

7675
def with_metaclass():
7776
class Factory: ...
7877
class Base(metaclass=Factory): ...
7978
class A(Base): ...
8079
a = A()
81-
a() # OK
80+
# TODO: resolve type hierarchy and metaclasses
81+
a() # Noncompliant
8282

8383

8484
def decorators():

0 commit comments

Comments
 (0)