Skip to content

Commit 2562c97

Browse files
SONARPY-1257 S1144: Should not raise on methods/classes with unknown decorator (#1362)
1 parent 01940a8 commit 2562c97

File tree

7 files changed

+81
-8
lines changed

7 files changed

+81
-8
lines changed

its/ruling/src/test/resources/expected/python-S1144.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
'project:biopython/Bio/Pathway/__init__.py':[
66
240,
77
],
8-
'project:django-2.2.3/django/utils/functional.py':[
9-
161,
10-
],
118
'project:numpy-1.16.4/numpy/lib/_version.py':[
129
155,
1310
],

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,44 @@
1919
*/
2020
package org.sonar.python.checks;
2121

22+
import java.util.Collection;
2223
import java.util.Optional;
24+
import java.util.function.Predicate;
2325
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2426
import org.sonar.plugins.python.api.SubscriptionContext;
2527
import org.sonar.plugins.python.api.symbols.AmbiguousSymbol;
28+
import org.sonar.plugins.python.api.symbols.ClassSymbol;
2629
import org.sonar.plugins.python.api.symbols.Symbol;
2730
import org.sonar.plugins.python.api.symbols.Usage;
2831
import org.sonar.plugins.python.api.tree.ClassDef;
32+
import org.sonar.python.tree.TreeUtils;
2933

3034
import static org.sonar.plugins.python.api.tree.Tree.Kind.CLASSDEF;
31-
import static org.sonar.python.tree.TreeUtils.getClassSymbolFromDef;
3235

3336
public abstract class AbstractUnreadPrivateMembersCheck extends PythonSubscriptionCheck {
3437

3538
@Override
3639
public void initialize(Context context) {
3740
String memberPrefix = memberPrefix();
3841
context.registerSyntaxNodeConsumer(CLASSDEF, ctx -> {
39-
ClassDef classDef = (ClassDef) ctx.syntaxNode();
40-
Optional.ofNullable(getClassSymbolFromDef(classDef)).ifPresent(classSymbol -> classSymbol.declaredMembers().stream()
42+
Optional.of(ctx.syntaxNode())
43+
.map(ClassDef.class::cast)
44+
// avoid checking for classes with decorators since it is impossible to analyze its final behavior
45+
.filter(classDef -> classDef.decorators().isEmpty())
46+
.map(TreeUtils::getClassSymbolFromDef)
47+
.map(ClassSymbol::declaredMembers)
48+
.stream()
49+
.flatMap(Collection::stream)
4150
.filter(s -> s.name().startsWith(memberPrefix) && !s.name().endsWith("__") && equalsToKind(s) && isNeverRead(s))
42-
.forEach(symbol -> reportIssue(ctx, symbol)));
51+
.filter(Predicate.not(this::isException))
52+
.forEach(symbol -> reportIssue(ctx, symbol));
4353
});
4454
}
4555

56+
protected boolean isException(Symbol symbol) {
57+
return false;
58+
}
59+
4660
private boolean equalsToKind(Symbol symbol) {
4761
if (symbol.kind().equals(Symbol.Kind.AMBIGUOUS)) {
4862
return ((AmbiguousSymbol) symbol).alternatives().stream().allMatch(s -> s.kind() == kind());

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
*/
2020
package org.sonar.python.checks;
2121

22+
import java.util.Optional;
2223
import org.sonar.check.Rule;
24+
import org.sonar.plugins.python.api.symbols.ClassSymbol;
2325
import org.sonar.plugins.python.api.symbols.Symbol;
2426

2527
import static org.sonar.plugins.python.api.symbols.Symbol.Kind.CLASS;
@@ -31,6 +33,15 @@ String memberPrefix() {
3133
return "_";
3234
}
3335

36+
@Override
37+
protected boolean isException(Symbol symbol) {
38+
return Optional.of(symbol)
39+
.filter(ClassSymbol.class::isInstance)
40+
.map(ClassSymbol.class::cast)
41+
.filter(ClassSymbol::hasDecorators)
42+
.isPresent();
43+
}
44+
3445
@Override
3546
Symbol.Kind kind() {
3647
return CLASS;

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@
1919
*/
2020
package org.sonar.python.checks;
2121

22+
import java.util.Collection;
23+
import java.util.Optional;
24+
import java.util.function.Predicate;
2225
import org.sonar.check.Rule;
26+
import org.sonar.plugins.python.api.symbols.FunctionSymbol;
2327
import org.sonar.plugins.python.api.symbols.Symbol;
28+
import org.sonar.python.semantic.BuiltinSymbols;
2429

2530
import static org.sonar.plugins.python.api.symbols.Symbol.Kind.FUNCTION;
2631

@@ -45,4 +50,15 @@ String message(String memberName) {
4550
String secondaryMessage() {
4651
return null;
4752
}
53+
54+
@Override
55+
protected boolean isException(Symbol symbol) {
56+
return Optional.of(symbol)
57+
.filter(FunctionSymbol.class::isInstance)
58+
.map(FunctionSymbol.class::cast)
59+
.map(FunctionSymbol::decorators)
60+
.stream()
61+
.flatMap(Collection::stream)
62+
.anyMatch(Predicate.not(BuiltinSymbols.STATIC_AND_CLASS_METHOD_DECORATORS::contains));
63+
}
4864
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ class __unused_cls: ... # Noncompliant {{Remove this unused private '__unused_cl
88

99
class _unused_cls: ... # Noncompliant
1010

11+
@unknown
12+
class _unused_decorated_cls: ...
13+
1114
class __used_cls: ...
1215

1316
class __other_used_cls: ...
1417

1518
def __init__(self):
1619
print(self.__used_cls)
17-
print(A.__other_used_cls)
20+
print(A.__other_used_cls)

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,30 @@ def __init__(self):
2828
print(self.__used())
2929
print(A.__used_cls_method())
3030
print(A.__used_static_method())
31+
32+
33+
def method_decorator(func):
34+
def inner(*args, **kwargs):
35+
return func(*args, **kwargs)
36+
return inner
37+
38+
class ClassWithMethodDecorator:
39+
@method_decorator
40+
def __getVariable(self):
41+
return self._v
42+
43+
@classmethod
44+
def __setVariable(self, v): # Noncompliant
45+
self._v = v
46+
47+
def __printVariable(self): # Noncompliant
48+
print(self._v)
49+
50+
def class_decorator(c):
51+
return c
52+
53+
@class_decorator
54+
class ClassWithDecorator:
55+
56+
def __printVariable(self):
57+
print(self.__v)

python-frontend/src/main/java/org/sonar/python/semantic/BuiltinSymbols.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,9 @@ public static Set<String> all() {
338338
all.addAll(BuiltinSymbols.MODULE_ATTRIBUTES);
339339
return all;
340340
}
341+
342+
public static final String CLASS_METHOD_DECORATOR = "classmethod";
343+
public static final String STATIC_METHOD_DECORATOR = "staticmethod";
344+
345+
public static final Set<String> STATIC_AND_CLASS_METHOD_DECORATORS = Set.of(CLASS_METHOD_DECORATOR, STATIC_METHOD_DECORATOR);
341346
}

0 commit comments

Comments
 (0)