Skip to content

Commit fbb290b

Browse files
SONARPY-902 S930 (ArgumentNumberCheck) should not report on instance … (#1065)
1 parent dc0f40b commit fbb290b

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.HashMap;
2323
import java.util.List;
2424
import java.util.Map;
25+
import java.util.Optional;
2526
import java.util.Set;
2627
import java.util.stream.Collectors;
2728
import javax.annotation.Nullable;
@@ -35,13 +36,16 @@
3536
import org.sonar.plugins.python.api.tree.Argument;
3637
import org.sonar.plugins.python.api.tree.CallExpression;
3738
import org.sonar.plugins.python.api.tree.Expression;
39+
import org.sonar.plugins.python.api.tree.FunctionDef;
3840
import org.sonar.plugins.python.api.tree.Name;
3941
import org.sonar.plugins.python.api.tree.QualifiedExpression;
4042
import org.sonar.plugins.python.api.tree.RegularArgument;
4143
import org.sonar.plugins.python.api.tree.Tree;
4244
import org.sonar.python.semantic.FunctionSymbolImpl;
4345
import org.sonar.python.tree.TreeUtils;
4446

47+
import static org.sonar.plugins.python.api.symbols.Usage.Kind.PARAMETER;
48+
4549
@Rule(key = "S930")
4650
public class ArgumentNumberCheck extends PythonSubscriptionCheck {
4751

@@ -66,7 +70,7 @@ public void initialize(Context context) {
6670

6771
private static void checkPositionalParameters(SubscriptionContext ctx, CallExpression callExpression, FunctionSymbol functionSymbol) {
6872
int self = 0;
69-
if (functionSymbol.isInstanceMethod() && callExpression.callee().is(Tree.Kind.QUALIFIED_EXPR)) {
73+
if (functionSymbol.isInstanceMethod() && callExpression.callee().is(Tree.Kind.QUALIFIED_EXPR) && !isCalledAsClassMethod((QualifiedExpression) callExpression.callee())) {
7074
self = 1;
7175
}
7276
Map<String, FunctionSymbol.Parameter> positionalParamsWithoutDefault = positionalParamsWithoutDefault(functionSymbol);
@@ -101,6 +105,25 @@ private static void checkPositionalParameters(SubscriptionContext ctx, CallExpre
101105
}
102106
}
103107

108+
private static boolean isCalledAsClassMethod(QualifiedExpression callee) {
109+
return TreeUtils.getSymbolFromTree(callee.qualifier())
110+
.filter(ArgumentNumberCheck::isParamOfClassMethod)
111+
.isPresent();
112+
}
113+
114+
// no need to check that's the first parameter (i.e. cls)
115+
// the assumption is that another method can be called only using the first parameter of a class method
116+
private static boolean isParamOfClassMethod(Symbol symbol) {
117+
return symbol.usages().stream().anyMatch(usage -> usage.kind() == PARAMETER && isParamOfClassMethod(usage.tree()));
118+
}
119+
120+
private static boolean isParamOfClassMethod(Tree tree) {
121+
FunctionDef functionDef = (FunctionDef) TreeUtils.firstAncestorOfKind(tree, Tree.Kind.FUNCDEF);
122+
return Optional.ofNullable(TreeUtils.getFunctionSymbolFromDef(functionDef))
123+
.filter(functionSymbol -> functionSymbol.decorators().stream().anyMatch(dec -> dec.equals("classmethod")))
124+
.isPresent();
125+
}
126+
104127
private static Map<String, FunctionSymbol.Parameter> positionalParamsWithoutDefault(FunctionSymbol functionSymbol) {
105128
int unnamedIndex = 0;
106129
Map<String, FunctionSymbol.Parameter> result = new HashMap<>();

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ def class_meth(cls, p1, p2): pass
9090
def static_meth(p1, p2): pass
9191
def foo(p1): pass
9292
foo(42)
93+
@classmethod
94+
def bar_class_method(cls):
95+
cls.bar_instance_method(cls)
96+
def bar_instance_method(self): pass
9397

9498
A.class_meth(42) # FN {{'class_meth' expects 2 positional arguments, but 1 was provided}}
9599

0 commit comments

Comments
 (0)